@linxin666/dsh-pet 0.1.13 → 0.1.14

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/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AFFINITY_MAX, c as applyTurnReward, d as rankOf, i as rowOf, l as defaultAffinityConfig, n as animationForPhase, o as AFFINITY_RANKS, r as defaultPetStateConfig, s as applyInteraction, t as PetStateMachine, u as emptyAffinity } from "./state-C9EyycwI.js";
1
+ import { a as AFFINITY_MAX, c as applyTurnReward, d as rankOf, i as rowOf, l as defaultAffinityConfig, n as animationForPhase, o as AFFINITY_RANKS, r as defaultPetStateConfig, s as applyInteraction, t as PetStateMachine, u as emptyAffinity } from "./state-P-wsVJ6O.js";
2
2
  import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
3
3
  import z from "schemastery";
4
4
  import { Service } from "@deepseek-ai/cordis";
@@ -162,6 +162,116 @@ function savePetPersist(data, dir = petHomeDir()) {
162
162
  writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
163
163
  renameSync(tmp, target);
164
164
  }
165
+ /** Keep tool names readable inside the compact status bubble. */
166
+ function displayToolName(name) {
167
+ const compact = name.replace(/\s+/g, " ").trim() || "工具";
168
+ return compact.length <= 24 ? compact : `${compact.slice(0, 21)}...`;
169
+ }
170
+ /** Whether a legacy phase is part of the pet's supported vocabulary. */
171
+ function isActivityPhase(phase) {
172
+ return [
173
+ "idle",
174
+ "waiting",
175
+ "thinking",
176
+ "tool",
177
+ "review",
178
+ "done",
179
+ "failed"
180
+ ].includes(phase);
181
+ }
182
+ /**
183
+ * Project the durable DSH session vocabulary into the pet's visual phases.
184
+ * Unknown and log-only events do not disturb the last meaningful activity.
185
+ */
186
+ function projectOfficialEvent(event, runtime) {
187
+ switch (event.type) {
188
+ case "turn/start":
189
+ runtime.activeTools.clear();
190
+ runtime.stepHadFailure = false;
191
+ return { input: {
192
+ phase: "waiting",
193
+ line: "准备开始"
194
+ } };
195
+ case "step/start":
196
+ runtime.activeTools.clear();
197
+ runtime.stepHadFailure = false;
198
+ return { input: {
199
+ phase: "waiting",
200
+ line: "等待模型响应"
201
+ } };
202
+ case "assistant/chunk": {
203
+ const { chunk } = event.data;
204
+ if (chunk.type === "reasoning-delta" && chunk.text.length > 0) return { input: {
205
+ phase: "thinking",
206
+ line: "正在思考"
207
+ } };
208
+ if (chunk.type === "text-delta" && chunk.text.length > 0) return { input: {
209
+ phase: "review",
210
+ line: "整理回复中"
211
+ } };
212
+ return;
213
+ }
214
+ case "assistant/message": return { input: {
215
+ phase: "review",
216
+ line: "整理回复中"
217
+ } };
218
+ case "tool/call":
219
+ runtime.activeTools.add(String(event.data.callId));
220
+ return { input: {
221
+ phase: "tool",
222
+ line: `正在使用 ${displayToolName(event.data.name)}`
223
+ } };
224
+ case "tool/result": {
225
+ const block = event.data.message.content[0];
226
+ runtime.activeTools.delete(String(event.data.message.source.callId));
227
+ runtime.stepHadFailure ||= event.data.error !== void 0 || block.isError === true;
228
+ if (runtime.activeTools.size > 0) return { input: {
229
+ phase: "tool",
230
+ line: `还有 ${runtime.activeTools.size} 个工具运行中`
231
+ } };
232
+ return runtime.stepHadFailure ? { input: {
233
+ phase: "failed",
234
+ line: "工具执行失败"
235
+ } } : { input: {
236
+ phase: "thinking",
237
+ line: "处理工具结果"
238
+ } };
239
+ }
240
+ case "turn/end":
241
+ runtime.activeTools.clear();
242
+ switch (event.data.reason.kind) {
243
+ case "completed": return {
244
+ input: {
245
+ phase: "done",
246
+ line: "完成啦"
247
+ },
248
+ completedTurn: event.data.turn
249
+ };
250
+ case "error": return { input: {
251
+ phase: "failed",
252
+ line: "执行失败"
253
+ } };
254
+ case "max-tokens": return { input: {
255
+ phase: "failed",
256
+ line: "达到输出上限"
257
+ } };
258
+ case "interrupted": return { input: {
259
+ phase: "failed",
260
+ line: "执行意外中断"
261
+ } };
262
+ case "blocked": return { input: {
263
+ phase: "waiting",
264
+ line: "等待继续"
265
+ } };
266
+ case "aborted": return { input: {
267
+ phase: "idle",
268
+ line: "已停止"
269
+ } };
270
+ default: return { input: { phase: "idle" } };
271
+ }
272
+ default: return;
273
+ }
274
+ }
165
275
  /**
166
276
  * Cordis service exposing the pet RPC domain. Lazy: nothing is scanned or
167
277
  * written until a query or interaction arrives; event listeners update only
@@ -179,6 +289,10 @@ var PetService = class extends Service {
179
289
  rewardedTurns = /* @__PURE__ */ new Map();
180
290
  enabled;
181
291
  disposeActivity;
292
+ /** Session whose most recent meaningful event currently drives the global pet. */
293
+ displaySession;
294
+ sessionActivity = /* @__PURE__ */ new WeakMap();
295
+ lastLegacyTurnRewardAt = 0;
182
296
  constructor(ctx, config = {}) {
183
297
  super(ctx, "pet");
184
298
  this.persistDir = config.persistDir ?? petHomeDir();
@@ -227,31 +341,26 @@ var PetService = class extends Service {
227
341
  if (!this.enabled) return;
228
342
  this.disposeActivity = (() => {
229
343
  const disposers = [this.ctx.on("session/event", (session, event) => {
230
- switch (event.type) {
231
- case "turn/start":
232
- this.machine.onSessionActive();
233
- break;
234
- case "step/start":
235
- this.machine.onSessionActive();
236
- this.machine.onActivityStatus({ phase: "thinking" });
237
- break;
238
- case "tool/call":
239
- this.machine.onSessionActive();
240
- this.machine.onActivityStatus({
241
- phase: "tool",
242
- line: "tool: " + event.data.name
243
- });
244
- break;
245
- case "turn/end":
246
- this.machine.onSessionActive();
247
- if (event.data.reason.kind === "completed") {
248
- this.machine.onActivityStatus({ phase: "done" });
249
- this.rewardTurn(String(session.id), event.data.turn);
250
- } else this.machine.onActivityStatus({ phase: "idle" });
251
- break;
252
- default: break;
344
+ const runtime = this.activityRuntime(session);
345
+ if (event.type === "activity/status") {
346
+ const payload = event.data ?? {};
347
+ if (typeof payload.phase !== "string" || !isActivityPhase(payload.phase)) return;
348
+ this.applyActivity(session, {
349
+ phase: payload.phase,
350
+ ...typeof payload.line === "string" ? { line: payload.line } : {},
351
+ ...typeof payload.phrase === "string" ? { phrase: payload.phrase } : {}
352
+ });
353
+ if (payload.phase === "done" && !runtime.officialEventsSeen) this.rewardLegacyTurn();
354
+ return;
253
355
  }
254
- }), this.ctx.on("session/disposed", () => {
356
+ const transition = projectOfficialEvent(event, runtime);
357
+ if (transition === void 0) return;
358
+ runtime.officialEventsSeen = true;
359
+ this.applyActivity(session, transition.input);
360
+ if (transition.completedTurn !== void 0) this.rewardTurn(String(session.id), transition.completedTurn);
361
+ }), this.ctx.on("session/disposed", (session) => {
362
+ if (session !== this.displaySession) return;
363
+ this.displaySession = void 0;
255
364
  this.machine.onSessionDisposed();
256
365
  })];
257
366
  return () => {
@@ -259,6 +368,25 @@ var PetService = class extends Service {
259
368
  };
260
369
  })();
261
370
  }
371
+ /** Return the projection state associated with one live session. */
372
+ activityRuntime(session) {
373
+ let runtime = this.sessionActivity.get(session);
374
+ if (runtime === void 0) {
375
+ runtime = {
376
+ activeTools: /* @__PURE__ */ new Set(),
377
+ officialEventsSeen: false,
378
+ stepHadFailure: false
379
+ };
380
+ this.sessionActivity.set(session, runtime);
381
+ }
382
+ return runtime;
383
+ }
384
+ /** Commit one activity as the host-global pet's most recent display state. */
385
+ applyActivity(session, input) {
386
+ this.displaySession = session;
387
+ this.machine.onActivityStatus(input);
388
+ this.machine.onSessionActive();
389
+ }
262
390
  /** RPC: pet or feed the pet. */
263
391
  async interact(kind) {
264
392
  const nowMs = Date.now();
@@ -388,6 +516,17 @@ var PetService = class extends Service {
388
516
  rewardTurn(sessionId, turn) {
389
517
  if (turn <= (this.rewardedTurns.get(sessionId) ?? 0)) return;
390
518
  this.rewardedTurns.set(sessionId, turn);
519
+ this.applyTurnReward();
520
+ }
521
+ /** Preserve turn rewards for installations that only emit legacy activity. */
522
+ rewardLegacyTurn() {
523
+ const nowMs = Date.now();
524
+ if (nowMs - this.lastLegacyTurnRewardAt < 5e3) return;
525
+ this.lastLegacyTurnRewardAt = nowMs;
526
+ this.applyTurnReward();
527
+ }
528
+ /** Persist one accepted completed-turn reward. */
529
+ applyTurnReward() {
391
530
  this.persist = {
392
531
  ...this.persist,
393
532
  affinity: applyTurnReward(this.persist.affinity, this.affinityConfig)
package/lib/invariant.js CHANGED
@@ -1,4 +1,4 @@
1
- import { l as defaultAffinityConfig, n as animationForPhase, o as AFFINITY_RANKS } from "./state-C9EyycwI.js";
1
+ import { l as defaultAffinityConfig, n as animationForPhase, o as AFFINITY_RANKS } from "./state-P-wsVJ6O.js";
2
2
  //#region src/invariant.ts
3
3
  /**
4
4
  * Package invariants — cheap structural checks run at import time on the
@@ -112,20 +112,21 @@ function applyTurnReward(state, config = defaultAffinityConfig) {
112
112
  const defaultPetStateConfig = { celebrateMs: 2400 };
113
113
  /**
114
114
  * Map one activity phase onto the animation contract.
115
- * - thinking / tool → `running` (focused work), with `running-right` as the
116
- * side-alternating variant the client may use for tool activity.
115
+ * - thinking → `running` and tool `running-right` (focused work).
116
+ * - review `review` while answer text is streaming.
117
117
  * - waiting → `waiting` (expectant pose, needs user input).
118
118
  * - done → `jumping` (celebration), then back to `idle` after the window.
119
+ * - failed → `failed` until another activity event arrives.
119
120
  * - idle → `idle` (calm breathing loop).
120
- * `failed` has no DSH phase source yet; the machine keeps the mapping table
121
- * so a future error event can light it up.
122
121
  */
123
122
  function animationForPhase(phase) {
124
123
  switch (phase) {
125
124
  case "thinking": return "running";
126
125
  case "tool": return "running-right";
126
+ case "review": return "review";
127
127
  case "waiting": return "waiting";
128
128
  case "done": return "jumping";
129
+ case "failed": return "failed";
129
130
  case "idle": return "idle";
130
131
  }
131
132
  }
@@ -159,7 +160,7 @@ var PetStateMachine = class {
159
160
  this.config = config;
160
161
  this.now = now;
161
162
  }
162
- /** Consume one phase snapshot (fed by the service from session events). */
163
+ /** Consume one projected activity update. */
163
164
  onActivityStatus(input) {
164
165
  this.phase = input.phase;
165
166
  this.line = input.line;
@@ -184,7 +185,7 @@ var PetStateMachine = class {
184
185
  let animation = animationForPhase(this.phase);
185
186
  if (this.phase === "done" && this.doneAt !== void 0) if (nowMs - this.doneAt < this.config.celebrateMs) animation = "jumping";
186
187
  else animation = "idle";
187
- const bubble = this.phrase ?? this.line;
188
+ const bubble = this.phase === "done" && this.doneAt !== void 0 && nowMs - this.doneAt >= this.config.celebrateMs ? void 0 : this.phrase ?? this.line;
188
189
  return {
189
190
  animation,
190
191
  ...bubble === void 0 ? {} : { bubble },
@@ -1 +1 @@
1
- {"version":3,"file":"WhalePet.d.ts","sourceRoot":"","sources":["../../../src/client/WhalePet.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAqC,WAAW,EAAE,MAAM,OAAO,CAAA;AAE3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAGjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,iFAAiF;AACjF,eAAO,MAAM,mBAAmB,gCAAgC,CAAA;AAEhE,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,wBAAwB,CAAA;AAErD,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,kCAAkC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAOD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,WAAW,CAmT1D"}
1
+ {"version":3,"file":"WhalePet.d.ts","sourceRoot":"","sources":["../../../src/client/WhalePet.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAqC,WAAW,EAAE,MAAM,OAAO,CAAA;AAE3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAGjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,iFAAiF;AACjF,eAAO,MAAM,mBAAmB,gCAAgC,CAAA;AAEhE,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,wBAAwB,CAAA;AAErD,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,kCAAkC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAOD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,WAAW,CAyT1D"}
@@ -198,6 +198,7 @@ export function WhalePet(props) {
198
198
  const pos = dragPos ?? { right: display.right, bottom: display.bottom };
199
199
  const spriteWidth = Math.round(FRAME_WIDTH * spriteScale);
200
200
  const spriteHeight = Math.round(FRAME_HEIGHT * spriteScale);
201
+ const statusBubble = feedback === null && !hovered ? snapshot?.bubble : undefined;
201
202
  const float = (_jsxs("div", { ref: floatRef, className: styles.float, style: { right: pos.right, bottom: pos.bottom, zIndex: 2147483000 }, onPointerEnter: () => {
202
203
  clearHideTimer();
203
204
  setHovered(true);
@@ -228,7 +229,7 @@ export function WhalePet(props) {
228
229
  if (draggedRef.current)
229
230
  return;
230
231
  props.onPet();
231
- }, role: "button", "aria-label": "whale girl" }), feedback !== null && (_jsx("div", { className: `${styles.bubble} ${feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet}`, children: feedback.text }, feedback.at)), hovered && dragRef.current === null && (_jsx("div", { className: styles.panel, onPointerEnter: () => {
232
+ }, role: "button", "aria-label": "whale girl" }), feedback !== null && (_jsx("div", { className: `${styles.bubble} ${feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet}`, children: feedback.text }, feedback.at)), statusBubble !== undefined && (_jsx("div", { className: `${styles.bubble} ${styles.bubbleStatus}`, role: "status", "aria-live": "polite", children: statusBubble })), hovered && dragRef.current === null && (_jsx("div", { className: styles.panel, onPointerEnter: () => {
232
233
  // Reaching the panel (or its bridge) must cancel any hide timer
233
234
  // the container's pointerleave may have armed while the pointer
234
235
  // crossed the sliver between the sprite and the panel.
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Pet host service — the `pet.*` RPC domain. Owns the state machine wiring
3
- * (maps core rc.6 session events turn/step/tool boundaries and the
4
- * session lifecycle onto the pet phases), the affinity ledger, and the
5
- * persisted display config. The API gateway maps this service's methods onto
6
- * `pet.state` / `pet.interact` / `pet.setVisible` / `pet.setConfig`
7
- * for browser consumers.
3
+ * (projects official session events and accepts legacy `activity/status`), the
4
+ * affinity ledger, and the persisted display config. The API gateway maps
5
+ * this service's methods onto `pet.state` / `pet.interact` /
6
+ * `pet.setVisible` / `pet.setConfig` for browser consumers.
8
7
  * @module @linxin666/dsh-pet/service
9
8
  */
10
9
  import { Context, Service } from '@deepseek-ai/cordis';
@@ -108,6 +107,10 @@ export declare class PetService extends Service {
108
107
  private rewardedTurns;
109
108
  private enabled;
110
109
  private disposeActivity;
110
+ /** Session whose most recent meaningful event currently drives the global pet. */
111
+ private displaySession;
112
+ private readonly sessionActivity;
113
+ private lastLegacyTurnRewardAt;
111
114
  constructor(ctx: Context, config?: PetConfig);
112
115
  /** Whether the pet service consumes session activity while enabled. */
113
116
  isEnabled(): boolean;
@@ -120,6 +123,10 @@ export declare class PetService extends Service {
120
123
  /** Start or stop the session-activity listeners that drive the pet. */
121
124
  setEnabled(enabled: boolean): void;
122
125
  private syncActivity;
126
+ /** Return the projection state associated with one live session. */
127
+ private activityRuntime;
128
+ /** Commit one activity as the host-global pet's most recent display state. */
129
+ private applyActivity;
123
130
  /** RPC: pet or feed the pet. */
124
131
  interact(kind: PetInteraction): Promise<PetInteractResult>;
125
132
  /** RPC: show or hide the pet. */
@@ -151,6 +158,10 @@ export declare class PetService extends Service {
151
158
  private syncSettingsFromPet;
152
159
  /** Award the turn reward once per completed turn (idempotent per session + turn). */
153
160
  private rewardTurn;
161
+ /** Preserve turn rewards for installations that only emit legacy activity. */
162
+ private rewardLegacyTurn;
163
+ /** Persist one accepted completed-turn reward. */
164
+ private applyTurnReward;
154
165
  /**
155
166
  * Settle the treat economy (work + time output since the last settlement)
156
167
  * and persist whenever the ledger changed. A zero-gain first settlement
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Pet host service — the `pet.*` RPC domain. Owns the state machine wiring
3
- * (maps core rc.6 session events turn/step/tool boundaries and the
4
- * session lifecycle onto the pet phases), the affinity ledger, and the
5
- * persisted display config. The API gateway maps this service's methods onto
6
- * `pet.state` / `pet.interact` / `pet.setVisible` / `pet.setConfig`
7
- * for browser consumers.
3
+ * (projects official session events and accepts legacy `activity/status`), the
4
+ * affinity ledger, and the persisted display config. The API gateway maps
5
+ * this service's methods onto `pet.state` / `pet.interact` /
6
+ * `pet.setVisible` / `pet.setConfig` for browser consumers.
8
7
  * @module @linxin666/dsh-pet/service
9
8
  */
10
9
  import { Service } from '@deepseek-ai/cordis';
@@ -14,6 +13,93 @@ import { defaultTreatConfig, settleTreatGrants, consumeTreat, } from "./treats.j
14
13
  import { defaultPetStateConfig, PetStateMachine, } from "./state.js";
15
14
  /** Settings namespace of the pet capability. Spelled here rather than imported: the browser half spells the same value. */
16
15
  export const PET_SETTINGS_NAMESPACE = 'pet';
16
+ /** Keep tool names readable inside the compact status bubble. */
17
+ function displayToolName(name) {
18
+ const compact = name.replace(/\s+/g, ' ').trim() || '工具';
19
+ return compact.length <= 24 ? compact : `${compact.slice(0, 21)}...`;
20
+ }
21
+ /** Whether a legacy phase is part of the pet's supported vocabulary. */
22
+ function isActivityPhase(phase) {
23
+ return ['idle', 'waiting', 'thinking', 'tool', 'review', 'done', 'failed'].includes(phase);
24
+ }
25
+ /**
26
+ * Project the durable DSH session vocabulary into the pet's visual phases.
27
+ * Unknown and log-only events do not disturb the last meaningful activity.
28
+ */
29
+ function projectOfficialEvent(event, runtime) {
30
+ switch (event.type) {
31
+ case 'turn/start':
32
+ runtime.activeTools.clear();
33
+ runtime.stepHadFailure = false;
34
+ return { input: { phase: 'waiting', line: '准备开始' } };
35
+ case 'step/start':
36
+ runtime.activeTools.clear();
37
+ runtime.stepHadFailure = false;
38
+ return { input: { phase: 'waiting', line: '等待模型响应' } };
39
+ case 'assistant/chunk': {
40
+ const { chunk } = event.data;
41
+ if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
42
+ return { input: { phase: 'thinking', line: '正在思考' } };
43
+ }
44
+ if (chunk.type === 'text-delta' && chunk.text.length > 0) {
45
+ return { input: { phase: 'review', line: '整理回复中' } };
46
+ }
47
+ return undefined;
48
+ }
49
+ case 'assistant/message':
50
+ return { input: { phase: 'review', line: '整理回复中' } };
51
+ case 'tool/call':
52
+ runtime.activeTools.add(String(event.data.callId));
53
+ return {
54
+ input: {
55
+ phase: 'tool',
56
+ line: `正在使用 ${displayToolName(event.data.name)}`,
57
+ },
58
+ };
59
+ case 'tool/result': {
60
+ const block = event.data.message.content[0];
61
+ runtime.activeTools.delete(String(event.data.message.source.callId));
62
+ runtime.stepHadFailure ||= event.data.error !== undefined || block.isError === true;
63
+ if (runtime.activeTools.size > 0) {
64
+ return {
65
+ input: {
66
+ phase: 'tool',
67
+ line: `还有 ${runtime.activeTools.size} 个工具运行中`,
68
+ },
69
+ };
70
+ }
71
+ return runtime.stepHadFailure
72
+ ? { input: { phase: 'failed', line: '工具执行失败' } }
73
+ : { input: { phase: 'thinking', line: '处理工具结果' } };
74
+ }
75
+ case 'turn/end': {
76
+ runtime.activeTools.clear();
77
+ switch (event.data.reason.kind) {
78
+ case 'completed':
79
+ return {
80
+ input: { phase: 'done', line: '完成啦' },
81
+ completedTurn: event.data.turn,
82
+ };
83
+ case 'error':
84
+ return { input: { phase: 'failed', line: '执行失败' } };
85
+ case 'max-tokens':
86
+ return { input: { phase: 'failed', line: '达到输出上限' } };
87
+ case 'interrupted':
88
+ return { input: { phase: 'failed', line: '执行意外中断' } };
89
+ case 'blocked':
90
+ return { input: { phase: 'waiting', line: '等待继续' } };
91
+ case 'aborted':
92
+ return { input: { phase: 'idle', line: '已停止' } };
93
+ default:
94
+ // TurnEndReasonMap is merge-extensible; a newer ending must not
95
+ // leave the pet showing stale in-progress work.
96
+ return { input: { phase: 'idle' } };
97
+ }
98
+ }
99
+ default:
100
+ return undefined;
101
+ }
102
+ }
17
103
  /**
18
104
  * Cordis service exposing the pet RPC domain. Lazy: nothing is scanned or
19
105
  * written until a query or interaction arrives; event listeners update only
@@ -31,6 +117,10 @@ export class PetService extends Service {
31
117
  rewardedTurns = new Map();
32
118
  enabled;
33
119
  disposeActivity;
120
+ /** Session whose most recent meaningful event currently drives the global pet. */
121
+ displaySession;
122
+ sessionActivity = new WeakMap();
123
+ lastLegacyTurnRewardAt = 0;
34
124
  constructor(ctx, config = {}) {
35
125
  super(ctx, 'pet');
36
126
  this.persistDir = config.persistDir ?? petHomeDir();
@@ -75,38 +165,40 @@ export class PetService extends Service {
75
165
  this.disposeActivity = (() => {
76
166
  const disposers = [
77
167
  this.ctx.on('session/event', (session, event) => {
78
- // rc.6 publishes no 'activity/status' event (the working-activity
79
- // tracker is gone), so the pet derives its phases from the core
80
- // session vocabulary instead.
81
- switch (event.type) {
82
- case 'turn/start':
83
- this.machine.onSessionActive();
84
- break;
85
- case 'step/start':
86
- this.machine.onSessionActive();
87
- this.machine.onActivityStatus({ phase: 'thinking' });
88
- break;
89
- case 'tool/call':
90
- this.machine.onSessionActive();
91
- this.machine.onActivityStatus({ phase: 'tool', line: 'tool: ' + event.data.name });
92
- break;
93
- case 'turn/end':
94
- this.machine.onSessionActive();
95
- if (event.data.reason.kind === 'completed') {
96
- this.machine.onActivityStatus({ phase: 'done' });
97
- this.rewardTurn(String(session.id), event.data.turn);
98
- }
99
- else {
100
- // Aborted / failed turns clear the working pose instead of
101
- // freezing the pet on its last phase.
102
- this.machine.onActivityStatus({ phase: 'idle' });
103
- }
104
- break;
105
- default:
106
- break;
168
+ const runtime = this.activityRuntime(session);
169
+ // `activity/status` is an optional compatibility input. It is not
170
+ // declared as a durable event type by this package because current
171
+ // Harness installations publish the official session vocabulary.
172
+ if (event.type === 'activity/status') {
173
+ const payload = (event.data ?? {});
174
+ if (typeof payload.phase !== 'string' || !isActivityPhase(payload.phase))
175
+ return;
176
+ this.applyActivity(session, {
177
+ phase: payload.phase,
178
+ ...(typeof payload.line === 'string' ? { line: payload.line } : {}),
179
+ ...(typeof payload.phrase === 'string' ? { phrase: payload.phrase } : {}),
180
+ });
181
+ // On a legacy-only stream the compatibility event owns turn
182
+ // rewards. Once any official activity is observed, turn/end owns
183
+ // them and a derived legacy `done` cannot double-count.
184
+ if (payload.phase === 'done' && !runtime.officialEventsSeen) {
185
+ this.rewardLegacyTurn();
186
+ }
187
+ return;
188
+ }
189
+ const transition = projectOfficialEvent(event, runtime);
190
+ if (transition === undefined)
191
+ return;
192
+ runtime.officialEventsSeen = true;
193
+ this.applyActivity(session, transition.input);
194
+ if (transition.completedTurn !== undefined) {
195
+ this.rewardTurn(String(session.id), transition.completedTurn);
107
196
  }
108
197
  }),
109
- this.ctx.on('session/disposed', () => {
198
+ this.ctx.on('session/disposed', (session) => {
199
+ if (session !== this.displaySession)
200
+ return;
201
+ this.displaySession = undefined;
110
202
  this.machine.onSessionDisposed();
111
203
  }),
112
204
  ];
@@ -114,6 +206,25 @@ export class PetService extends Service {
114
206
  dispose(); };
115
207
  })();
116
208
  }
209
+ /** Return the projection state associated with one live session. */
210
+ activityRuntime(session) {
211
+ let runtime = this.sessionActivity.get(session);
212
+ if (runtime === undefined) {
213
+ runtime = {
214
+ activeTools: new Set(),
215
+ officialEventsSeen: false,
216
+ stepHadFailure: false,
217
+ };
218
+ this.sessionActivity.set(session, runtime);
219
+ }
220
+ return runtime;
221
+ }
222
+ /** Commit one activity as the host-global pet's most recent display state. */
223
+ applyActivity(session, input) {
224
+ this.displaySession = session;
225
+ this.machine.onActivityStatus(input);
226
+ this.machine.onSessionActive();
227
+ }
117
228
  /** RPC: pet or feed the pet. */
118
229
  async interact(kind) {
119
230
  const nowMs = Date.now();
@@ -212,6 +323,19 @@ export class PetService extends Service {
212
323
  if (turn <= last)
213
324
  return;
214
325
  this.rewardedTurns.set(sessionId, turn);
326
+ this.applyTurnReward();
327
+ }
328
+ /** Preserve turn rewards for installations that only emit legacy activity. */
329
+ rewardLegacyTurn() {
330
+ const nowMs = Date.now();
331
+ // A legacy `done` snapshot may repeat during the celebration window.
332
+ if (nowMs - this.lastLegacyTurnRewardAt < 5_000)
333
+ return;
334
+ this.lastLegacyTurnRewardAt = nowMs;
335
+ this.applyTurnReward();
336
+ }
337
+ /** Persist one accepted completed-turn reward. */
338
+ applyTurnReward() {
215
339
  this.persist = { ...this.persist, affinity: applyTurnReward(this.persist.affinity, this.affinityConfig) };
216
340
  this.flush();
217
341
  }