@linxin666/dsh-pet 0.3.19 → 0.3.20
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +70 -33
- package/lib/client.js.map +1 -1
- package/lib/index.js +75 -57
- package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
- package/lib/types/client/renderers/frames2d.js +77 -29
- package/lib/types/event-projection.d.ts +14 -0
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +32 -18
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +11 -1
- package/package.json +14 -14
- package/src/client/renderers/frames2d.test.ts +94 -4
- package/src/client/renderers/frames2d.ts +76 -26
- package/src/event-projection.ts +37 -18
- package/src/service.ts +11 -0
package/lib/index.js
CHANGED
|
@@ -876,30 +876,6 @@ function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
876
876
|
phase: "waiting",
|
|
877
877
|
line: runtime.voice.scene("waiting", nowMs)
|
|
878
878
|
} };
|
|
879
|
-
case "assistant/chunk": {
|
|
880
|
-
const { chunk } = event.data;
|
|
881
|
-
if (chunk.type === "reasoning-delta" && chunk.text.length > 0) {
|
|
882
|
-
const whisper = runtime.whispers.feed("thinking", nowMs);
|
|
883
|
-
return {
|
|
884
|
-
input: {
|
|
885
|
-
phase: "thinking",
|
|
886
|
-
line: runtime.voice.scene("thinking", nowMs)
|
|
887
|
-
},
|
|
888
|
-
...whisper === void 0 ? {} : { whisper }
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
if (chunk.type === "text-delta" && chunk.text.length > 0) {
|
|
892
|
-
const whisper = runtime.whispers.feed("writing", nowMs);
|
|
893
|
-
return {
|
|
894
|
-
input: {
|
|
895
|
-
phase: "review",
|
|
896
|
-
line: runtime.voice.scene("review", nowMs)
|
|
897
|
-
},
|
|
898
|
-
...whisper === void 0 ? {} : { whisper }
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
879
|
case "assistant/message": return { input: {
|
|
904
880
|
phase: "review",
|
|
905
881
|
line: runtime.voice.scene("review", nowMs)
|
|
@@ -990,6 +966,36 @@ function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
990
966
|
default: return;
|
|
991
967
|
}
|
|
992
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Project one live `agent/assistant-stream` publication into the pet's visual
|
|
971
|
+
* phases. Chunk frames are the alpha.2 replacement for the retired durable
|
|
972
|
+
* `assistant/chunk` event: a reasoning delta keeps the pet thinking, a text
|
|
973
|
+
* delta moves it to review; start, end, and non-delta chunks change nothing.
|
|
974
|
+
*/
|
|
975
|
+
function projectAssistantStreamFrame(frame, runtime, nowMs = Date.now()) {
|
|
976
|
+
if (frame.type !== "chunk") return void 0;
|
|
977
|
+
const { chunk } = frame;
|
|
978
|
+
if (chunk.type === "reasoning-delta" && chunk.text.length > 0) {
|
|
979
|
+
const whisper = runtime.whispers.feed("thinking", nowMs);
|
|
980
|
+
return {
|
|
981
|
+
input: {
|
|
982
|
+
phase: "thinking",
|
|
983
|
+
line: runtime.voice.scene("thinking", nowMs)
|
|
984
|
+
},
|
|
985
|
+
...whisper === void 0 ? {} : { whisper }
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
if (chunk.type === "text-delta" && chunk.text.length > 0) {
|
|
989
|
+
const whisper = runtime.whispers.feed("writing", nowMs);
|
|
990
|
+
return {
|
|
991
|
+
input: {
|
|
992
|
+
phase: "review",
|
|
993
|
+
line: runtime.voice.scene("review", nowMs)
|
|
994
|
+
},
|
|
995
|
+
...whisper === void 0 ? {} : { whisper }
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
}
|
|
993
999
|
//#endregion
|
|
994
1000
|
//#region src/treats.ts
|
|
995
1001
|
const defaultTreatConfig = {
|
|
@@ -4474,39 +4480,51 @@ var PetService = class extends Service {
|
|
|
4474
4480
|
}
|
|
4475
4481
|
if (!this.enabled) return;
|
|
4476
4482
|
this.disposeActivity = (() => {
|
|
4477
|
-
const disposers = [
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
this.
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
this.
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4483
|
+
const disposers = [
|
|
4484
|
+
this.ctx.on("session/event", (session, event) => {
|
|
4485
|
+
const runtime = this.activityOf(session).runtime;
|
|
4486
|
+
if (event.type === "activity/status") {
|
|
4487
|
+
const payload = event.data ?? {};
|
|
4488
|
+
if (typeof payload.phase !== "string" || !isActivityPhase(payload.phase)) return;
|
|
4489
|
+
this.applyActivity(session, {
|
|
4490
|
+
phase: payload.phase,
|
|
4491
|
+
...typeof payload.line === "string" ? { line: payload.line } : {},
|
|
4492
|
+
...typeof payload.phrase === "string" ? { phrase: payload.phrase } : {}
|
|
4493
|
+
});
|
|
4494
|
+
if (payload.phase === "done" && !runtime.officialEventsSeen) this.rewardLegacyTurn();
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
const transition = projectOfficialEvent(event, runtime);
|
|
4498
|
+
if (transition === void 0) return;
|
|
4499
|
+
runtime.officialEventsSeen = true;
|
|
4500
|
+
this.officialEventSessions.add(session);
|
|
4501
|
+
this.applyActivity(session, transition.input, transition.whisper);
|
|
4502
|
+
if (transition.completedTurn !== void 0) this.rewardTurn(String(session.id), transition.completedTurn);
|
|
4503
|
+
}),
|
|
4504
|
+
this.ctx.on("agent/assistant-stream", ({ agent, frame }) => {
|
|
4505
|
+
const session = agent.session;
|
|
4506
|
+
const runtime = this.activityOf(session).runtime;
|
|
4507
|
+
const transition = projectAssistantStreamFrame(frame, runtime);
|
|
4508
|
+
if (transition === void 0) return;
|
|
4509
|
+
runtime.officialEventsSeen = true;
|
|
4510
|
+
this.officialEventSessions.add(session);
|
|
4511
|
+
this.applyActivity(session, transition.input, transition.whisper);
|
|
4512
|
+
}),
|
|
4513
|
+
this.ctx.on("session/disposed", (session) => {
|
|
4514
|
+
this.ledger.forgetSession(String(session.id));
|
|
4515
|
+
this.officialEventSessions.delete(session);
|
|
4516
|
+
this.sessionActivity.delete(session);
|
|
4517
|
+
if (session !== this.displaySession) return;
|
|
4518
|
+
this.displaySession = void 0;
|
|
4519
|
+
const remaining = [...this.sessionActivity.entries()].at(-1);
|
|
4520
|
+
if (remaining !== void 0) {
|
|
4521
|
+
const [nextSession, activity] = remaining;
|
|
4522
|
+
this.displaySession = nextSession;
|
|
4523
|
+
if (activity.lastInput !== void 0) this.machine.onActivityStatus(activity.lastInput);
|
|
4524
|
+
this.machine.onSessionActive();
|
|
4525
|
+
} else this.machine.onSessionDisposed();
|
|
4526
|
+
})
|
|
4527
|
+
];
|
|
4510
4528
|
return () => {
|
|
4511
4529
|
for (const dispose of disposers) dispose();
|
|
4512
4530
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frames2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/frames2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAA4B,KAAK,WAAW,EAA2B,KAAK,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AACzI,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAC3C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjE,wEAAwE;IACxE,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAA;IAC9C,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,4DAA4D;AAC5D,MAAM,WAAW,6BAA6B;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IACzC;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IAC7C,2DAA2D;IAC3D,YAAY,IAAI,MAAM,CAAA;CACvB;AAuFD,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"frames2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/frames2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAA4B,KAAK,WAAW,EAA2B,KAAK,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AACzI,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAC3C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjE,wEAAwE;IACxE,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAA;IAC9C,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,4DAA4D;AAC5D,MAAM,WAAW,6BAA6B;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IACzC;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IAC7C,2DAA2D;IAC3D,YAAY,IAAI,MAAM,CAAA;CACvB;AAuFD,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,iBAAiB,CA6T3D,CAAA"}
|
|
@@ -159,37 +159,75 @@ export const frames2dRenderer = {
|
|
|
159
159
|
// the last painted frame instead of breaking playback).
|
|
160
160
|
const decoding = new Map();
|
|
161
161
|
const decodedAll = [];
|
|
162
|
-
|
|
162
|
+
// All frame fetches funnel through a small pool. Full-library warm passes
|
|
163
|
+
// on large pets fire 1100+ requests at once, which trips the browser's
|
|
164
|
+
// in-flight request limit (net::ERR_INSUFFICIENT_RESOURCES) and fails
|
|
165
|
+
// whole batches of frames while starving the rest of the page. Playback
|
|
166
|
+
// demand jumps ahead of the warm backlog.
|
|
167
|
+
const FRAME_POOL_LIMIT = 8;
|
|
168
|
+
const frameQueue = [];
|
|
169
|
+
let activeFrames = 0;
|
|
170
|
+
const decodeFrame = async (url) => {
|
|
171
|
+
try {
|
|
172
|
+
const response = await fetch(url);
|
|
173
|
+
if (!response.ok)
|
|
174
|
+
throw new Error('http ' + response.status);
|
|
175
|
+
const bitmap = await createImageBitmap(await response.blob());
|
|
176
|
+
return { source: bitmap, width: bitmap.width, height: bitmap.height };
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Fail-open: classic Image decode keeps non-modern runtimes alive.
|
|
180
|
+
return await new Promise((resolve) => {
|
|
181
|
+
try {
|
|
182
|
+
const pre = new Image();
|
|
183
|
+
pre.onload = () => {
|
|
184
|
+
resolve(pre.naturalWidth > 0 ? { source: pre, width: pre.naturalWidth, height: pre.naturalHeight } : undefined);
|
|
185
|
+
};
|
|
186
|
+
pre.onerror = () => resolve(undefined);
|
|
187
|
+
pre.src = url;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
resolve(undefined);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const pumpFrames = () => {
|
|
196
|
+
while (activeFrames < FRAME_POOL_LIMIT && frameQueue.length > 0) {
|
|
197
|
+
const queued = frameQueue.shift();
|
|
198
|
+
activeFrames += 1;
|
|
199
|
+
queued.release();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const loadFrame = (url, jump = false) => {
|
|
203
|
+
// Jump first: warm-enqueued frames already carry their memo, so a
|
|
204
|
+
// playback demand must reorder the unstarted entry before the cache
|
|
205
|
+
// lookup short-circuits.
|
|
206
|
+
if (jump) {
|
|
207
|
+
const index = frameQueue.findIndex((queued) => queued.url === url);
|
|
208
|
+
if (index > 0)
|
|
209
|
+
frameQueue.unshift(frameQueue.splice(index, 1)[0]);
|
|
210
|
+
}
|
|
163
211
|
const cached = decoding.get(url);
|
|
164
212
|
if (cached !== undefined)
|
|
165
213
|
return cached;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// Fail-open: classic Image decode keeps non-modern runtimes alive.
|
|
176
|
-
return await new Promise((resolve) => {
|
|
177
|
-
try {
|
|
178
|
-
const pre = new Image();
|
|
179
|
-
pre.onload = () => {
|
|
180
|
-
resolve(pre.naturalWidth > 0 ? { source: pre, width: pre.naturalWidth, height: pre.naturalHeight } : undefined);
|
|
181
|
-
};
|
|
182
|
-
pre.onerror = () => resolve(undefined);
|
|
183
|
-
pre.src = url;
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
resolve(undefined);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
})();
|
|
214
|
+
let release;
|
|
215
|
+
const gate = new Promise((resolve) => { release = resolve; });
|
|
216
|
+
const job = gate.then(() => (disposed ? undefined : decodeFrame(url)));
|
|
217
|
+
job.then((frame) => { if (frame === undefined)
|
|
218
|
+
decoding.delete(url); }, () => decoding.delete(url));
|
|
219
|
+
void job.finally(() => {
|
|
220
|
+
activeFrames -= 1;
|
|
221
|
+
pumpFrames();
|
|
222
|
+
});
|
|
191
223
|
decoding.set(url, job);
|
|
192
224
|
decodedAll.push(job.then(() => undefined, () => undefined));
|
|
225
|
+
const entry = { url, release };
|
|
226
|
+
if (jump)
|
|
227
|
+
frameQueue.unshift(entry);
|
|
228
|
+
else
|
|
229
|
+
frameQueue.push(entry);
|
|
230
|
+
pumpFrames();
|
|
193
231
|
return job;
|
|
194
232
|
};
|
|
195
233
|
let disposed = false;
|
|
@@ -217,7 +255,7 @@ export const frames2dRenderer = {
|
|
|
217
255
|
if (context2d === null || canvas === null)
|
|
218
256
|
return;
|
|
219
257
|
const myToken = ++drawToken;
|
|
220
|
-
void loadFrame(url).then((frame) => {
|
|
258
|
+
void loadFrame(url, true).then((frame) => {
|
|
221
259
|
if (disposed || frame === undefined || myToken !== drawToken)
|
|
222
260
|
return;
|
|
223
261
|
if (lastDrawnUrl === url)
|
|
@@ -323,7 +361,14 @@ export const frames2dRenderer = {
|
|
|
323
361
|
// Warm pass: decode every frame up front (tiny same-origin webp files)
|
|
324
362
|
// so loops and phase switches never wait on a first decode - same intent
|
|
325
363
|
// as the historical Image-cache warm loop, now feeding the decode cache.
|
|
326
|
-
|
|
364
|
+
// Phase-reachable tracks enqueue first so early switches never trail the
|
|
365
|
+
// full warm backlog; demand loads jump the queue regardless.
|
|
366
|
+
const warmTrackIds = [
|
|
367
|
+
...new Set([...Object.values(config.phases), config.phases.idle]),
|
|
368
|
+
];
|
|
369
|
+
for (const warmTrack of [...warmTrackIds, ...Object.keys(config.tracks)].map((id) => config.tracks[id])) {
|
|
370
|
+
if (warmTrack === undefined)
|
|
371
|
+
continue;
|
|
327
372
|
for (const warmUrl of warmTrack.frames)
|
|
328
373
|
void loadFrame(warmUrl);
|
|
329
374
|
}
|
|
@@ -340,7 +385,10 @@ export const frames2dRenderer = {
|
|
|
340
385
|
if (watchdog !== undefined)
|
|
341
386
|
clearInterval(watchdog);
|
|
342
387
|
// Release decoded bitmaps after pending decodes settle; close() is
|
|
343
|
-
// browser-only, so guard it for exotic hosts.
|
|
388
|
+
// browser-only, so guard it for exotic hosts. Queued-but-unstarted
|
|
389
|
+
// frames release immediately as no-ops so the settle barrier drains.
|
|
390
|
+
for (const queued of frameQueue.splice(0))
|
|
391
|
+
queued.release();
|
|
344
392
|
void Promise.allSettled(decodedAll).then(() => {
|
|
345
393
|
for (const job of decoding.values()) {
|
|
346
394
|
void job.then((frame) => {
|
|
@@ -11,8 +11,15 @@
|
|
|
11
11
|
* tool/result and turn/end — so the pet's inner voice always roughly knows
|
|
12
12
|
* what is going on and never mis-fires on output text. The wall clock is
|
|
13
13
|
* injected by the caller, keeping every projection reproducible.
|
|
14
|
+
*
|
|
15
|
+
* Since the 0.1.5-alpha.2 cohort the stream itself is no longer durable
|
|
16
|
+
* vocabulary: per-chunk phase input arrives through the process-local
|
|
17
|
+
* `agent/assistant-stream` publication ({@link projectAssistantStreamFrame}),
|
|
18
|
+
* while the durable log settles one `assistant/message` (or `assistant/attempt`)
|
|
19
|
+
* per attempt.
|
|
14
20
|
* @module @linxin666/dsh-pet/event-projection
|
|
15
21
|
*/
|
|
22
|
+
import type { AssistantStreamFrame } from '@deepseek-ai/dsh-agent';
|
|
16
23
|
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
17
24
|
import type { PetStateInput } from './state.ts';
|
|
18
25
|
import { StatusVoice, WhisperEngine, type VoicePoolsProvider } from './chatter.ts';
|
|
@@ -56,4 +63,11 @@ export declare function isActivityPhase(phase: string): phase is PetStateInput['
|
|
|
56
63
|
* @param nowMs - injected wall clock for copy rotation and whisper pacing.
|
|
57
64
|
*/
|
|
58
65
|
export declare function projectOfficialEvent(event: SessionEvent, runtime: ProjectionRuntime, nowMs?: number): PetActivityTransition | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Project one live `agent/assistant-stream` publication into the pet's visual
|
|
68
|
+
* phases. Chunk frames are the alpha.2 replacement for the retired durable
|
|
69
|
+
* `assistant/chunk` event: a reasoning delta keeps the pet thinking, a text
|
|
70
|
+
* delta moves it to review; start, end, and non-delta chunks change nothing.
|
|
71
|
+
*/
|
|
72
|
+
export declare function projectAssistantStreamFrame(frame: AssistantStreamFrame, runtime: ProjectionRuntime, nowMs?: number): PetActivityTransition | undefined;
|
|
59
73
|
//# sourceMappingURL=event-projection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,WAAW,EAGX,aAAa,EAGb,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAA;AAErB,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,+EAA+E;IAC/E,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,KAAK,EAAE,WAAW,CAAA;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CASpF;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAiGnC;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAkBnC"}
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* tool/result and turn/end — so the pet's inner voice always roughly knows
|
|
12
12
|
* what is going on and never mis-fires on output text. The wall clock is
|
|
13
13
|
* injected by the caller, keeping every projection reproducible.
|
|
14
|
+
*
|
|
15
|
+
* Since the 0.1.5-alpha.2 cohort the stream itself is no longer durable
|
|
16
|
+
* vocabulary: per-chunk phase input arrives through the process-local
|
|
17
|
+
* `agent/assistant-stream` publication ({@link projectAssistantStreamFrame}),
|
|
18
|
+
* while the durable log settles one `assistant/message` (or `assistant/attempt`)
|
|
19
|
+
* per attempt.
|
|
14
20
|
* @module @linxin666/dsh-pet/event-projection
|
|
15
21
|
*/
|
|
16
22
|
import { StatusVoice, toolArgHint, toolCategory, WhisperEngine, whisperCategoryOf, looksLikeTestTool, } from "./chatter.js";
|
|
@@ -55,24 +61,6 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
55
61
|
runtime.activeTools.clear();
|
|
56
62
|
runtime.stepHadFailure = false;
|
|
57
63
|
return { input: { phase: 'waiting', line: runtime.voice.scene('waiting', nowMs) } };
|
|
58
|
-
case 'assistant/chunk': {
|
|
59
|
-
const { chunk } = event.data;
|
|
60
|
-
if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
|
|
61
|
-
const whisper = runtime.whispers.feed('thinking', nowMs);
|
|
62
|
-
return {
|
|
63
|
-
input: { phase: 'thinking', line: runtime.voice.scene('thinking', nowMs) },
|
|
64
|
-
...(whisper === undefined ? {} : { whisper }),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
if (chunk.type === 'text-delta' && chunk.text.length > 0) {
|
|
68
|
-
const whisper = runtime.whispers.feed('writing', nowMs);
|
|
69
|
-
return {
|
|
70
|
-
input: { phase: 'review', line: runtime.voice.scene('review', nowMs) },
|
|
71
|
-
...(whisper === undefined ? {} : { whisper }),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
64
|
case 'assistant/message':
|
|
77
65
|
return { input: { phase: 'review', line: runtime.voice.scene('review', nowMs) } };
|
|
78
66
|
case 'tool/call': {
|
|
@@ -156,3 +144,29 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
156
144
|
return undefined;
|
|
157
145
|
}
|
|
158
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Project one live `agent/assistant-stream` publication into the pet's visual
|
|
149
|
+
* phases. Chunk frames are the alpha.2 replacement for the retired durable
|
|
150
|
+
* `assistant/chunk` event: a reasoning delta keeps the pet thinking, a text
|
|
151
|
+
* delta moves it to review; start, end, and non-delta chunks change nothing.
|
|
152
|
+
*/
|
|
153
|
+
export function projectAssistantStreamFrame(frame, runtime, nowMs = Date.now()) {
|
|
154
|
+
if (frame.type !== 'chunk')
|
|
155
|
+
return undefined;
|
|
156
|
+
const { chunk } = frame;
|
|
157
|
+
if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
|
|
158
|
+
const whisper = runtime.whispers.feed('thinking', nowMs);
|
|
159
|
+
return {
|
|
160
|
+
input: { phase: 'thinking', line: runtime.voice.scene('thinking', nowMs) },
|
|
161
|
+
...(whisper === undefined ? {} : { whisper }),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (chunk.type === 'text-delta' && chunk.text.length > 0) {
|
|
165
|
+
const whisper = runtime.whispers.feed('writing', nowMs);
|
|
166
|
+
return {
|
|
167
|
+
input: { phase: 'review', line: runtime.voice.scene('review', nowMs) },
|
|
168
|
+
...(whisper === undefined ? {} : { whisper }),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
|
package/lib/types/service.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { Service } from '@deepseek-ai/cordis';
|
|
15
15
|
import { announcementFresh, parseAnnouncement } from "./announce.js";
|
|
16
|
-
import { emptyProjectionRuntime, isActivityPhase, projectOfficialEvent, } from "./event-projection.js";
|
|
16
|
+
import { emptyProjectionRuntime, isActivityPhase, projectAssistantStreamFrame, projectOfficialEvent, } from "./event-projection.js";
|
|
17
17
|
import { PetLedger } from "./ledger.js";
|
|
18
18
|
import { DEFAULT_PET_NAME, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN, PET_NAME_MAX_LENGTH, loadPetPersist, petHomeDir, savePetPersist, } from "./persist.js";
|
|
19
19
|
import { DEFAULT_DECORATION_ID, decorationView, loadPetRegistry, petEntryView, petPackageRoot, } from "./registry.js";
|
|
@@ -236,6 +236,16 @@ export class PetService extends Service {
|
|
|
236
236
|
this.rewardTurn(String(session.id), transition.completedTurn);
|
|
237
237
|
}
|
|
238
238
|
}),
|
|
239
|
+
this.ctx.on('agent/assistant-stream', ({ agent, frame }) => {
|
|
240
|
+
const session = agent.session;
|
|
241
|
+
const runtime = this.activityOf(session).runtime;
|
|
242
|
+
const transition = projectAssistantStreamFrame(frame, runtime);
|
|
243
|
+
if (transition === undefined)
|
|
244
|
+
return;
|
|
245
|
+
runtime.officialEventsSeen = true;
|
|
246
|
+
this.officialEventSessions.add(session);
|
|
247
|
+
this.applyActivity(session, transition.input, transition.whisper);
|
|
248
|
+
}),
|
|
239
249
|
this.ctx.on('session/disposed', (session) => {
|
|
240
250
|
this.ledger.forgetSession(String(session.id));
|
|
241
251
|
this.officialEventSessions.delete(session);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.20",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"license": "Apache-2.0",
|
|
42
42
|
"dsh": {
|
|
43
43
|
"engines": {
|
|
44
|
-
"dsh": ">=0.1.
|
|
44
|
+
"dsh": ">=0.1.5-rc.1"
|
|
45
45
|
},
|
|
46
46
|
"bundle": {
|
|
47
47
|
"patch": "./cordis.patch.yml"
|
|
@@ -66,18 +66,18 @@
|
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
69
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.
|
|
70
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.
|
|
71
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.
|
|
72
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
73
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
78
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
79
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
69
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.5-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-client-store": "^0.1.5-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-session": "^0.1.5-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
|
|
81
81
|
"@testing-library/dom": "^10.4.1",
|
|
82
82
|
"@testing-library/react": "^16.3.2",
|
|
83
83
|
"@types/node": "^22.20.0",
|
|
@@ -182,10 +182,8 @@ describe('frames2dRenderer', () => {
|
|
|
182
182
|
describe('frames2dRenderer canvas bitmap path', () => {
|
|
183
183
|
// The canvas branch needs createImageBitmap + fetch + a real 2D context;
|
|
184
184
|
// jsdom has none of them, so each test installs fakes and restores after.
|
|
185
|
-
const flush = async (): Promise<void> => {
|
|
186
|
-
await Promise.resolve()
|
|
187
|
-
await Promise.resolve()
|
|
188
|
-
await Promise.resolve()
|
|
185
|
+
const flush = async (rounds = 12): Promise<void> => {
|
|
186
|
+
for (let i = 0; i < rounds; i += 1) await Promise.resolve()
|
|
189
187
|
}
|
|
190
188
|
|
|
191
189
|
let draws = 0
|
|
@@ -278,4 +276,96 @@ describe('frames2dRenderer canvas bitmap path', () => {
|
|
|
278
276
|
expect(draws).toBeGreaterThanOrEqual(3)
|
|
279
277
|
handle.dispose()
|
|
280
278
|
})
|
|
279
|
+
|
|
280
|
+
it('drains the warm pass through a bounded fetch pool', async () => {
|
|
281
|
+
let inFlight = 0
|
|
282
|
+
let peak = 0
|
|
283
|
+
const gates: Array<() => void> = []
|
|
284
|
+
vi.stubGlobal('fetch', vi.fn(async () => {
|
|
285
|
+
inFlight += 1
|
|
286
|
+
peak = Math.max(peak, inFlight)
|
|
287
|
+
await new Promise<void>((resolve) => gates.push(resolve))
|
|
288
|
+
inFlight -= 1
|
|
289
|
+
return { ok: true, blob: async () => ({}) }
|
|
290
|
+
}))
|
|
291
|
+
const bigConfig: PetFrames2dConfig = {
|
|
292
|
+
tracks: {
|
|
293
|
+
idle: { frames: Array.from({ length: 24 }, (_, i) => `/pet/miku/idle/${i}.webp`), durations: Array.from({ length: 24 }, () => 100), loop: true },
|
|
294
|
+
},
|
|
295
|
+
phases: { idle: 'idle' },
|
|
296
|
+
}
|
|
297
|
+
const { ctx } = canvasSetup()
|
|
298
|
+
const handle = frames2dRenderer.mount(ctx, frames2dRenderer.validateConfig(bigConfig)) as Frames2dRendererHandle
|
|
299
|
+
await flush()
|
|
300
|
+
expect(peak).toBeLessThanOrEqual(8)
|
|
301
|
+
expect(gates.length).toBe(8)
|
|
302
|
+
for (let i = 0; i < 64 && gates.length > 0; i += 1) {
|
|
303
|
+
gates.shift()!()
|
|
304
|
+
await flush()
|
|
305
|
+
}
|
|
306
|
+
expect(peak).toBeLessThanOrEqual(8)
|
|
307
|
+
expect(bitmaps.length).toBe(24)
|
|
308
|
+
handle.dispose()
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('jumps a playback-demand frame ahead of the warm backlog', async () => {
|
|
312
|
+
const fetched: string[] = []
|
|
313
|
+
const held = new Map<string, () => void>()
|
|
314
|
+
vi.stubGlobal('fetch', vi.fn(async (input: unknown) => {
|
|
315
|
+
const url = String(input)
|
|
316
|
+
fetched.push(url)
|
|
317
|
+
if (fetched.length <= 8) {
|
|
318
|
+
await new Promise<void>((resolve) => { held.set(url, resolve) })
|
|
319
|
+
}
|
|
320
|
+
return { ok: true, blob: async () => ({}) }
|
|
321
|
+
}))
|
|
322
|
+
const wideConfig: PetFrames2dConfig = {
|
|
323
|
+
tracks: {
|
|
324
|
+
idle: { frames: Array.from({ length: 10 }, (_, i) => `/pet/miku/idle/${i}.webp`), durations: Array.from({ length: 10 }, () => 100), loop: true },
|
|
325
|
+
happy: { frames: ['/pet/miku/happy/1.webp'], durations: [100], loop: false, fallback: 'idle' },
|
|
326
|
+
},
|
|
327
|
+
phases: { idle: 'idle', done: 'happy' },
|
|
328
|
+
}
|
|
329
|
+
const { ctx } = canvasSetup()
|
|
330
|
+
const handle = frames2dRenderer.mount(ctx, frames2dRenderer.validateConfig(wideConfig)) as Frames2dRendererHandle
|
|
331
|
+
await flush()
|
|
332
|
+
expect(fetched.length).toBe(8)
|
|
333
|
+
// The happy frame sits behind the idle backlog; playing it must pull its
|
|
334
|
+
// fetch ahead of the not-yet-started warm frames.
|
|
335
|
+
handle.setState('happy')
|
|
336
|
+
await flush()
|
|
337
|
+
// Free one pool slot: the released idle decode completes and the freed
|
|
338
|
+
// slot must start the jumped happy frame, not the next warm idle frame.
|
|
339
|
+
held.get(fetched[0])?.()
|
|
340
|
+
await flush()
|
|
341
|
+
expect(fetched[8]).toBe('/pet/miku/happy/1.webp')
|
|
342
|
+
for (const release of held.values()) release()
|
|
343
|
+
await flush()
|
|
344
|
+
handle.dispose()
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('retries a failed frame instead of memoizing the failure forever', async () => {
|
|
348
|
+
let failuresLeft = 1
|
|
349
|
+
vi.stubGlobal('fetch', vi.fn(async () => {
|
|
350
|
+
if (failuresLeft > 0) {
|
|
351
|
+
failuresLeft -= 1
|
|
352
|
+
throw new Error('transient')
|
|
353
|
+
}
|
|
354
|
+
return { ok: true, blob: async () => ({}) }
|
|
355
|
+
}))
|
|
356
|
+
class FailingImage {
|
|
357
|
+
onload: (() => void) | null = null
|
|
358
|
+
onerror: (() => void) | null = null
|
|
359
|
+
set src(_value: string) { queueMicrotask(() => this.onerror?.()) }
|
|
360
|
+
}
|
|
361
|
+
vi.stubGlobal('Image', FailingImage)
|
|
362
|
+
const { ctx } = canvasSetup()
|
|
363
|
+
const handle = frames2dRenderer.mount(ctx, frames2dRenderer.validateConfig(CONFIG)) as Frames2dRendererHandle
|
|
364
|
+
// Warm pass fetches fail through the pool, resolve undefined and are
|
|
365
|
+
// dropped from the memo; nothing throws.
|
|
366
|
+
await flush()
|
|
367
|
+
const memoFetches = (fetch as ReturnType<typeof vi.fn>).mock.calls.length
|
|
368
|
+
expect(memoFetches).toBeGreaterThan(0)
|
|
369
|
+
handle.dispose()
|
|
370
|
+
})
|
|
281
371
|
})
|