@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/README.i18n.yaml +2 -2
- package/README.md +11 -7
- package/README.zh.md +11 -7
- package/lib/client.js +9 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +164 -25
- package/lib/invariant.js +1 -1
- package/lib/{state-C9EyycwI.js → state-P-wsVJ6O.js} +7 -6
- package/lib/types/client/WhalePet.d.ts.map +1 -1
- package/lib/types/client/WhalePet.js +2 -1
- package/lib/types/service.d.ts +16 -5
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +159 -35
- package/lib/types/state.d.ts +10 -12
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +14 -10
- package/package.json +1 -1
- package/src/client/WhalePet.test.tsx +25 -1
- package/src/client/WhalePet.tsx +6 -0
- package/src/client/pet.module.css +9 -0
- package/src/service.ts +187 -34
- package/src/state.test.ts +4 -1
- package/src/state.ts +17 -13
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-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
116
|
-
*
|
|
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
|
|
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,
|
|
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.
|
package/lib/types/service.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pet host service — the `pet.*` RPC domain. Owns the state machine wiring
|
|
3
|
-
* (
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `pet.
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
|
package/lib/types/service.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pet host service — the `pet.*` RPC domain. Owns the state machine wiring
|
|
3
|
-
* (
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `pet.
|
|
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
|
-
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
}
|