@linxin666/dsh-pet 0.3.3 → 0.3.5
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 +30 -13
- package/README.zh.md +30 -13
- package/assets/ouo-neko/pet.json +31 -0
- package/assets/ouo-neko/previews/failed.gif +0 -0
- package/assets/ouo-neko/previews/idle.gif +0 -0
- package/assets/ouo-neko/previews/jumping.gif +0 -0
- package/assets/ouo-neko/previews/review.gif +0 -0
- package/assets/ouo-neko/previews/running-left.gif +0 -0
- package/assets/ouo-neko/previews/running-right.gif +0 -0
- package/assets/ouo-neko/previews/running.gif +0 -0
- package/assets/ouo-neko/previews/waiting.gif +0 -0
- package/assets/ouo-neko/previews/waving.gif +0 -0
- package/assets/ouo-neko/spritesheet.webp +0 -0
- package/contracts/pet-manifest-v2.schema.json +102 -1
- package/contracts/voice-pack-v1.schema.json +23 -28
- package/lib/client.js +781 -40
- package/lib/client.js.map +1 -1
- package/lib/index.js +1567 -482
- package/lib/types/chatter.d.ts +68 -46
- package/lib/types/chatter.d.ts.map +1 -1
- package/lib/types/chatter.js +243 -301
- package/lib/types/client/PetDockEntry.d.ts +3 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +18 -2
- package/lib/types/client/PetSprite.d.ts +23 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +49 -45
- package/lib/types/client/PluginSettingsCard.d.ts +2 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +2 -2
- package/lib/types/client/drag-stream.d.ts +19 -0
- package/lib/types/client/drag-stream.d.ts.map +1 -0
- package/lib/types/client/drag-stream.js +26 -0
- package/lib/types/client/gameplay-hud.d.ts +50 -0
- package/lib/types/client/gameplay-hud.d.ts.map +1 -0
- package/lib/types/client/gameplay-hud.js +274 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +38 -9
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -0
- package/lib/types/client/pet-store.d.ts +7 -1
- package/lib/types/client/pet-store.d.ts.map +1 -1
- package/lib/types/client/pet-store.js +4 -0
- package/lib/types/client/renderers/Frames2dVisualMount.d.ts +29 -0
- package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -0
- package/lib/types/client/renderers/Frames2dVisualMount.js +83 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts +6 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -1
- package/lib/types/client/renderers/PetRendererSwitch.js +14 -1
- package/lib/types/client/renderers/frames2d.d.ts +36 -0
- package/lib/types/client/renderers/frames2d.d.ts.map +1 -0
- package/lib/types/client/renderers/frames2d.js +196 -0
- package/lib/types/defaults.d.ts +6 -0
- package/lib/types/defaults.d.ts.map +1 -0
- package/lib/types/defaults.js +5 -0
- package/lib/types/dsh-home.d.ts.map +1 -1
- package/lib/types/dsh-home.js +9 -3
- package/lib/types/event-projection.d.ts +9 -4
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +46 -14
- package/lib/types/gameplay.d.ts +182 -0
- package/lib/types/gameplay.d.ts.map +1 -0
- package/lib/types/gameplay.js +618 -0
- package/lib/types/ledger.d.ts +14 -0
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +30 -0
- package/lib/types/manifest-v2.d.ts +38 -1
- package/lib/types/manifest-v2.d.ts.map +1 -1
- package/lib/types/manifest-v2.js +182 -2
- package/lib/types/persist.d.ts +4 -4
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +41 -4
- package/lib/types/registry.d.ts +29 -0
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +170 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +30 -3
- package/lib/types/service.d.ts +63 -11
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +208 -19
- package/lib/types/voice-pack.d.ts +6 -7
- package/lib/types/voice-pack.d.ts.map +1 -1
- package/lib/types/voice-pack.js +44 -47
- package/package.json +5 -2
- package/src/chatter.test.ts +68 -38
- package/src/chatter.ts +269 -316
- package/src/client/PetDockEntry.test.tsx +6 -0
- package/src/client/PetDockEntry.tsx +32 -1
- package/src/client/PetSprite.test.tsx +68 -21
- package/src/client/PetSprite.tsx +63 -45
- package/src/client/PluginSettingsCard.tsx +4 -2
- package/src/client/drag-stream.ts +35 -0
- package/src/client/gameplay-hud.test.tsx +338 -0
- package/src/client/gameplay-hud.tsx +403 -0
- package/src/client/index.test.tsx +10 -1
- package/src/client/index.ts +46 -11
- package/src/client/locales.ts +36 -0
- package/src/client/pet-store.ts +10 -1
- package/src/client/pet.module.css +192 -29
- package/src/client/renderers/Frames2dVisualMount.tsx +111 -0
- package/src/client/renderers/PetRendererSwitch.tsx +28 -1
- package/src/client/renderers/frames2d.test.ts +137 -0
- package/src/client/renderers/frames2d.ts +216 -0
- package/src/client/settings-card.module.css +5 -0
- package/src/defaults.ts +7 -0
- package/src/dsh-home.ts +9 -3
- package/src/event-projection.ts +57 -16
- package/src/gameplay.test.ts +239 -0
- package/src/gameplay.ts +692 -0
- package/src/ledger.test.ts +16 -0
- package/src/ledger.ts +31 -0
- package/src/manifest-v2.test.ts +130 -1
- package/src/manifest-v2.ts +181 -2
- package/src/persist.test.ts +3 -0
- package/src/persist.ts +43 -6
- package/src/registry.test.ts +168 -1
- package/src/registry.ts +199 -1
- package/src/routes.ts +28 -4
- package/src/service.ts +240 -29
- package/src/voice-pack.test.ts +48 -25
- package/src/voice-pack.ts +50 -47
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Frames2d visual mount — the React bridge between the pet center chrome
|
|
4
|
+
* and the imperative frames2d renderer, mirroring the live2d mount. The
|
|
5
|
+
* bridge owns the contract context (asset base, phase stream, interaction
|
|
6
|
+
* write-back, activation cleanups), feeds the polled phase into the stream,
|
|
7
|
+
* forwards the chrome's drag gesture onto the conventional 'drag' track
|
|
8
|
+
* (when the pet declares one), and renders the localized fallback card when
|
|
9
|
+
* the served config is invalid.
|
|
10
|
+
* @module @linxin666/dsh-pet/client/renderers/Frames2dVisualMount
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useRef, useState } from 'react';
|
|
13
|
+
import { createPhaseStream } from "../phase-stream.js";
|
|
14
|
+
import { defaultPetRendererRegistry } from "./registry.js";
|
|
15
|
+
/** Mount the frames2d renderer as the sprite's visual (inside the chrome). */
|
|
16
|
+
export function Frames2dVisualMount(props) {
|
|
17
|
+
const containerRef = useRef(null);
|
|
18
|
+
const streamRef = useRef(null);
|
|
19
|
+
const handleRef = useRef(null);
|
|
20
|
+
const [invalid, setInvalid] = useState(false);
|
|
21
|
+
// One activation per pet definition: build the contract context and mount.
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setInvalid(false);
|
|
24
|
+
const container = containerRef.current;
|
|
25
|
+
const frames2d = props.definition.frames2d;
|
|
26
|
+
if (container === null || frames2d === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
streamRef.current ??= createPhaseStream(props.phase);
|
|
29
|
+
const cleanups = [];
|
|
30
|
+
const ctx = {
|
|
31
|
+
petId: props.definition.id,
|
|
32
|
+
assetBase: '/pet/' + encodeURIComponent(props.definition.id),
|
|
33
|
+
container,
|
|
34
|
+
phase: streamRef.current,
|
|
35
|
+
interact: props.onPet,
|
|
36
|
+
onCleanup: (fn) => { cleanups.push(fn); },
|
|
37
|
+
};
|
|
38
|
+
let handle;
|
|
39
|
+
try {
|
|
40
|
+
handle = defaultPetRendererRegistry.mount('frames2d', ctx, frames2d);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
setInvalid(true);
|
|
44
|
+
return () => { for (const fn of cleanups.splice(0))
|
|
45
|
+
fn(); };
|
|
46
|
+
}
|
|
47
|
+
handleRef.current = handle;
|
|
48
|
+
// The gameplay HUD steers one shared override slot through the bus;
|
|
49
|
+
// mode rules (work blocks drag, sleep wakes on it) keep the two
|
|
50
|
+
// producers from fighting over the slot.
|
|
51
|
+
if (props.bus !== undefined) {
|
|
52
|
+
const gameplayBus = props.bus;
|
|
53
|
+
gameplayBus.setTrack = (track) => { handleRef.current?.setState(track); };
|
|
54
|
+
cleanups.push(() => { gameplayBus.setTrack = undefined; });
|
|
55
|
+
}
|
|
56
|
+
// The drag gesture drives the conventional 'drag' track when declared.
|
|
57
|
+
// On release, a declared gameplay.dragEndState (miku: standup) plays
|
|
58
|
+
// once; its fallback auto-releases the override back to the phase map.
|
|
59
|
+
const dragTrack = props.definition.gameplay?.dragState ?? (frames2d.tracks.drag === undefined ? undefined : 'drag');
|
|
60
|
+
const offDrag = props.drag.subscribe((dragging) => {
|
|
61
|
+
if (dragTrack === undefined)
|
|
62
|
+
return;
|
|
63
|
+
if (dragging) {
|
|
64
|
+
handle.setState(dragTrack);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
handle.setState(props.definition.gameplay?.dragEndState);
|
|
68
|
+
});
|
|
69
|
+
cleanups.push(offDrag);
|
|
70
|
+
return () => {
|
|
71
|
+
handleRef.current = null;
|
|
72
|
+
for (const fn of cleanups.splice(0))
|
|
73
|
+
fn();
|
|
74
|
+
handle.dispose();
|
|
75
|
+
};
|
|
76
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- one activation per pet identity
|
|
77
|
+
}, [props.definition]);
|
|
78
|
+
// Feed the polled phase into the activation's stream (change-only).
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
streamRef.current?.push(props.phase);
|
|
81
|
+
}, [props.phase]);
|
|
82
|
+
return (_jsx("div", { ref: containerRef, "data-dsh-pet-frames2d": props.definition.id, style: { width: '100%', height: '100%', pointerEvents: 'none' }, children: invalid && (_jsx("span", { "data-dsh-pet-frames2d-error": "invalid-config", children: props.t('pet.renderer.unavailable', { renderer: 'frames2d' }) })) }));
|
|
83
|
+
}
|
|
@@ -11,6 +11,8 @@ import { type ReactElement, type ReactNode } from 'react';
|
|
|
11
11
|
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
12
12
|
import type { PetDefinition } from '../../registry.ts';
|
|
13
13
|
import type { ActivityPhase } from '../../state.ts';
|
|
14
|
+
import { type DragStream } from '../drag-stream.ts';
|
|
15
|
+
import type { GameplayBus } from '../gameplay-hud.tsx';
|
|
14
16
|
import type { NS } from '../locales.ts';
|
|
15
17
|
/** Dispatch one pet definition to its renderer; unknown kinds get a card. */
|
|
16
18
|
export declare function PetRendererSwitch(props: {
|
|
@@ -19,6 +21,10 @@ export declare function PetRendererSwitch(props: {
|
|
|
19
21
|
phase: ActivityPhase;
|
|
20
22
|
/** The chrome's pet interaction (affinity write-back owner). */
|
|
21
23
|
onPet: () => void;
|
|
24
|
+
/** External drag stream (gameplay HUD shares it); created locally otherwise. */
|
|
25
|
+
drag?: DragStream;
|
|
26
|
+
/** Gameplay coordination bus forwarded to the frames2d visual mount. */
|
|
27
|
+
bus?: GameplayBus;
|
|
22
28
|
t: PropsLocale<typeof NS>['t'];
|
|
23
29
|
children?: ReactNode;
|
|
24
30
|
}): ReactElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PetRendererSwitch.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/PetRendererSwitch.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"PetRendererSwitch.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/PetRendererSwitch.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAwC,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC/F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAItD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAEvC,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,UAAU,EAAE,aAAa,CAAA;IACzB,wDAAwD;IACxD,KAAK,EAAE,aAAa,CAAA;IACpB,gEAAgE;IAChE,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gFAAgF;IAChF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,wEAAwE;IACxE,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA;IAC9B,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,GAAG,YAAY,CAuCf"}
|
|
@@ -8,14 +8,27 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
|
8
8
|
* diagnostic card instead of blanking.
|
|
9
9
|
* @module @linxin666/dsh-pet/client/renderers/PetRendererSwitch
|
|
10
10
|
*/
|
|
11
|
-
import { cloneElement, isValidElement } from 'react';
|
|
11
|
+
import { cloneElement, isValidElement, useRef } from 'react';
|
|
12
|
+
import { createDragStream } from "../drag-stream.js";
|
|
12
13
|
import { defaultPetRendererRegistry } from "./registry.js";
|
|
13
14
|
import { Live2dVisualMount } from "./live2d/Live2dVisualMount.js";
|
|
15
|
+
import { Frames2dVisualMount } from "./Frames2dVisualMount.js";
|
|
14
16
|
/** Dispatch one pet definition to its renderer; unknown kinds get a card. */
|
|
15
17
|
export function PetRendererSwitch(props) {
|
|
16
18
|
const renderer = props.definition.renderer ?? 'sprite2d';
|
|
19
|
+
// The frames2d drag stream lives one level up: the chrome (PetSprite)
|
|
20
|
+
// reports the gesture through onDraggingChange, the visual subscribes.
|
|
21
|
+
const dragRef = useRef(null);
|
|
22
|
+
if (dragRef.current === null || dragRef.current.id !== props.definition.id) {
|
|
23
|
+
dragRef.current = { id: props.definition.id, stream: createDragStream() };
|
|
24
|
+
}
|
|
25
|
+
const drag = props.drag ?? dragRef.current.stream;
|
|
17
26
|
if (renderer === 'sprite2d')
|
|
18
27
|
return _jsx(_Fragment, { children: props.children });
|
|
28
|
+
if (renderer === 'frames2d' && defaultPetRendererRegistry.has('frames2d') && isValidElement(props.children)) {
|
|
29
|
+
const visual = (_jsx(Frames2dVisualMount, { definition: props.definition, phase: props.phase, onPet: props.onPet, drag: drag, ...(props.bus === undefined ? {} : { bus: props.bus }), t: props.t }));
|
|
30
|
+
return cloneElement(props.children, { visual, onDraggingChange: (dragging) => drag.push(dragging) });
|
|
31
|
+
}
|
|
19
32
|
if (renderer === 'live2d' && defaultPetRendererRegistry.has('live2d') && isValidElement(props.children)) {
|
|
20
33
|
const visual = (_jsx(Live2dVisualMount, { definition: props.definition, phase: props.phase, onPet: props.onPet, t: props.t }));
|
|
21
34
|
return cloneElement(props.children, { visual });
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* frames2d renderer — plays the free-form named frame-sequence tracks of a
|
|
3
|
+
* frames2d pet (manifest v2 'frames2d' block). The pet center picks the
|
|
4
|
+
* track from the ActivityPhase stream (phase -> track map, idle fallback);
|
|
5
|
+
* the gameplay driver may force a track through the handle's setState
|
|
6
|
+
* override (drag/work/sleep/shop...), and a finished non-loop track settles
|
|
7
|
+
* into its fallback, releasing the override when the fallback matches the
|
|
8
|
+
* phase-mapped track. Rendering never throws: a broken track list degrades
|
|
9
|
+
* to the first decodable frame, and the 1.2 s stall watchdog re-kicks the
|
|
10
|
+
* playback chain after timer throttling.
|
|
11
|
+
* @module @linxin666/dsh-pet/client/renderers/frames2d
|
|
12
|
+
*/
|
|
13
|
+
import { type PetRenderer, type PetRendererHandle } from '../../contracts/renderer.ts';
|
|
14
|
+
import type { ActivityPhase } from '../../state.ts';
|
|
15
|
+
/** One track as served inside the pet definition (browser URLs). */
|
|
16
|
+
export interface Frames2dTrackConfig {
|
|
17
|
+
frames: string[];
|
|
18
|
+
durations: number[];
|
|
19
|
+
loop: boolean;
|
|
20
|
+
fallback?: string;
|
|
21
|
+
}
|
|
22
|
+
/** The frames2d block as served inside the pet definition. */
|
|
23
|
+
export interface PetFrames2dConfig {
|
|
24
|
+
tracks: Record<string, Frames2dTrackConfig>;
|
|
25
|
+
phases: Partial<Record<ActivityPhase, string>> & {
|
|
26
|
+
idle: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface Frames2dRendererHandle extends PetRendererHandle {
|
|
30
|
+
/** Force a track id (gameplay override); undefined returns to phase mapping. */
|
|
31
|
+
setState(track: string | undefined): void;
|
|
32
|
+
/** The track currently playing (diagnostics and tests). */
|
|
33
|
+
currentTrack(): string;
|
|
34
|
+
}
|
|
35
|
+
export declare const frames2dRenderer: PetRenderer<PetFrames2dConfig>;
|
|
36
|
+
//# sourceMappingURL=frames2d.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frames2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/frames2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;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;CAClE;AAED,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IACzC,2DAA2D;IAC3D,YAAY,IAAI,MAAM,CAAA;CACvB;AAoCD,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,iBAAiB,CAgJ3D,CAAA"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* frames2d renderer — plays the free-form named frame-sequence tracks of a
|
|
3
|
+
* frames2d pet (manifest v2 'frames2d' block). The pet center picks the
|
|
4
|
+
* track from the ActivityPhase stream (phase -> track map, idle fallback);
|
|
5
|
+
* the gameplay driver may force a track through the handle's setState
|
|
6
|
+
* override (drag/work/sleep/shop...), and a finished non-loop track settles
|
|
7
|
+
* into its fallback, releasing the override when the fallback matches the
|
|
8
|
+
* phase-mapped track. Rendering never throws: a broken track list degrades
|
|
9
|
+
* to the first decodable frame, and the 1.2 s stall watchdog re-kicks the
|
|
10
|
+
* playback chain after timer throttling.
|
|
11
|
+
* @module @linxin666/dsh-pet/client/renderers/frames2d
|
|
12
|
+
*/
|
|
13
|
+
import { PET_RENDERER_API_VERSION } from "../../contracts/renderer.js";
|
|
14
|
+
/** Stall watchdog period: a looping track stuck longer re-kicks its chain. */
|
|
15
|
+
const WATCHDOG_MS = 1200;
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
/** Fail-closed client-side validation of the served frames2d block. */
|
|
20
|
+
function validateFrames2dConfig(config) {
|
|
21
|
+
if (!isRecord(config) || !isRecord(config.tracks) || !isRecord(config.phases)) {
|
|
22
|
+
throw new Error('frames2d config requires tracks and phases objects');
|
|
23
|
+
}
|
|
24
|
+
const tracks = {};
|
|
25
|
+
for (const [name, raw] of Object.entries(config.tracks)) {
|
|
26
|
+
if (!isRecord(raw) || !Array.isArray(raw.frames) || raw.frames.length === 0
|
|
27
|
+
|| raw.frames.some(f => typeof f !== 'string' || f === '')
|
|
28
|
+
|| !Array.isArray(raw.durations) || raw.durations.length !== raw.frames.length
|
|
29
|
+
|| raw.durations.some(d => typeof d !== 'number' || !(d > 0))) {
|
|
30
|
+
throw new Error('frames2d track ' + JSON.stringify(name) + ' needs same-length frames/durations');
|
|
31
|
+
}
|
|
32
|
+
tracks[name] = {
|
|
33
|
+
frames: raw.frames,
|
|
34
|
+
durations: raw.durations,
|
|
35
|
+
loop: raw.loop !== false,
|
|
36
|
+
...(typeof raw.fallback === 'string' ? { fallback: raw.fallback } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const phases = config.phases;
|
|
40
|
+
if (typeof phases.idle !== 'string' || tracks[phases.idle] === undefined) {
|
|
41
|
+
throw new Error('frames2d phases.idle must name an existing track');
|
|
42
|
+
}
|
|
43
|
+
return { tracks, phases: phases };
|
|
44
|
+
}
|
|
45
|
+
export const frames2dRenderer = {
|
|
46
|
+
id: 'frames2d',
|
|
47
|
+
apiVersion: PET_RENDERER_API_VERSION,
|
|
48
|
+
validateConfig: validateFrames2dConfig,
|
|
49
|
+
mount(ctx, config) {
|
|
50
|
+
const reducedMotion = typeof window !== 'undefined'
|
|
51
|
+
&& typeof window.matchMedia === 'function'
|
|
52
|
+
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
53
|
+
const img = document.createElement('img');
|
|
54
|
+
img.dataset.dshPetFrames2d = ctx.petId;
|
|
55
|
+
img.alt = '';
|
|
56
|
+
img.draggable = false;
|
|
57
|
+
img.style.width = '100%';
|
|
58
|
+
img.style.height = '100%';
|
|
59
|
+
img.style.objectFit = 'contain';
|
|
60
|
+
img.style.pointerEvents = 'none';
|
|
61
|
+
ctx.container.appendChild(img);
|
|
62
|
+
// Warm the browser cache for every frame up front (same-origin, small
|
|
63
|
+
// webp files); playback itself swaps img.src without React state.
|
|
64
|
+
for (const track of Object.values(config.tracks)) {
|
|
65
|
+
for (const url of track.frames) {
|
|
66
|
+
const pre = new Image();
|
|
67
|
+
pre.src = url;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let disposed = false;
|
|
71
|
+
let timer;
|
|
72
|
+
let watchdog;
|
|
73
|
+
let track = config.phases.idle;
|
|
74
|
+
let frameIndex = 0;
|
|
75
|
+
let lastAdvance = Date.now();
|
|
76
|
+
let override;
|
|
77
|
+
const trackForPhase = (phase) => {
|
|
78
|
+
const mapped = config.phases[phase];
|
|
79
|
+
return mapped !== undefined && config.tracks[mapped] !== undefined ? mapped : config.phases.idle;
|
|
80
|
+
};
|
|
81
|
+
const show = (trackId, index) => {
|
|
82
|
+
const def = config.tracks[trackId];
|
|
83
|
+
const url = def?.frames[index];
|
|
84
|
+
if (url !== undefined && img.getAttribute('src') !== url)
|
|
85
|
+
img.src = url;
|
|
86
|
+
};
|
|
87
|
+
const schedule = (ms) => {
|
|
88
|
+
if (disposed)
|
|
89
|
+
return;
|
|
90
|
+
timer = setTimeout(tick, ms);
|
|
91
|
+
};
|
|
92
|
+
function tick() {
|
|
93
|
+
if (disposed)
|
|
94
|
+
return;
|
|
95
|
+
const def = config.tracks[track];
|
|
96
|
+
if (def === undefined || def.frames.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
lastAdvance = Date.now();
|
|
99
|
+
const next = frameIndex + 1;
|
|
100
|
+
if (next < def.frames.length) {
|
|
101
|
+
frameIndex = next;
|
|
102
|
+
show(track, frameIndex);
|
|
103
|
+
schedule(def.durations[frameIndex] ?? 200);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (def.loop) {
|
|
107
|
+
frameIndex = 0;
|
|
108
|
+
show(track, frameIndex);
|
|
109
|
+
schedule(def.durations[frameIndex] ?? 200);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Non-loop completion: settle into the fallback; when the fallback is
|
|
113
|
+
// what the phase map would play anyway, release the gameplay override.
|
|
114
|
+
const target = def.fallback !== undefined && config.tracks[def.fallback] !== undefined
|
|
115
|
+
? def.fallback
|
|
116
|
+
: config.phases.idle;
|
|
117
|
+
if (target === trackForPhase(ctx.phase.get()))
|
|
118
|
+
override = undefined;
|
|
119
|
+
play(target);
|
|
120
|
+
}
|
|
121
|
+
function play(trackId) {
|
|
122
|
+
if (disposed)
|
|
123
|
+
return;
|
|
124
|
+
if (config.tracks[trackId] === undefined)
|
|
125
|
+
trackId = config.phases.idle;
|
|
126
|
+
if (timer !== undefined)
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
track = trackId;
|
|
129
|
+
frameIndex = 0;
|
|
130
|
+
lastAdvance = Date.now();
|
|
131
|
+
show(track, frameIndex);
|
|
132
|
+
if (reducedMotion)
|
|
133
|
+
return;
|
|
134
|
+
const def = config.tracks[track];
|
|
135
|
+
schedule(def.durations[0] ?? 200);
|
|
136
|
+
}
|
|
137
|
+
const unsubscribe = ctx.phase.subscribe((phase) => {
|
|
138
|
+
if (override !== undefined)
|
|
139
|
+
return;
|
|
140
|
+
const target = trackForPhase(phase);
|
|
141
|
+
if (target !== track)
|
|
142
|
+
play(target);
|
|
143
|
+
});
|
|
144
|
+
// Watchdog: a throttled-away timer (background tab) leaves the chain
|
|
145
|
+
// dead; re-kick when a looping track has not advanced on schedule.
|
|
146
|
+
if (!reducedMotion) {
|
|
147
|
+
watchdog = setInterval(() => {
|
|
148
|
+
if (disposed)
|
|
149
|
+
return;
|
|
150
|
+
const def = config.tracks[track];
|
|
151
|
+
if (def === undefined || !def.loop)
|
|
152
|
+
return;
|
|
153
|
+
const expected = (def.durations[frameIndex] ?? 200) + WATCHDOG_MS;
|
|
154
|
+
if (Date.now() - lastAdvance > expected)
|
|
155
|
+
tick();
|
|
156
|
+
}, WATCHDOG_MS);
|
|
157
|
+
}
|
|
158
|
+
play(track);
|
|
159
|
+
let disposedOnce = false;
|
|
160
|
+
const dispose = () => {
|
|
161
|
+
if (disposedOnce)
|
|
162
|
+
return;
|
|
163
|
+
disposedOnce = true;
|
|
164
|
+
disposed = true;
|
|
165
|
+
unsubscribe();
|
|
166
|
+
if (timer !== undefined)
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
if (watchdog !== undefined)
|
|
169
|
+
clearInterval(watchdog);
|
|
170
|
+
img.remove();
|
|
171
|
+
};
|
|
172
|
+
ctx.onCleanup(dispose);
|
|
173
|
+
return {
|
|
174
|
+
dispose,
|
|
175
|
+
setState(next) {
|
|
176
|
+
if (disposed)
|
|
177
|
+
return;
|
|
178
|
+
if (next === undefined) {
|
|
179
|
+
override = undefined;
|
|
180
|
+
const target = trackForPhase(ctx.phase.get());
|
|
181
|
+
if (target !== track)
|
|
182
|
+
play(target);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (config.tracks[next] === undefined)
|
|
186
|
+
return;
|
|
187
|
+
override = next;
|
|
188
|
+
if (next !== track)
|
|
189
|
+
play(next);
|
|
190
|
+
},
|
|
191
|
+
currentTrack() {
|
|
192
|
+
return track;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Shared defaults used by registry selection and persisted-state migration. */
|
|
2
|
+
/** Pet id that legacy single-pet installs and fresh registries resolve to. */
|
|
3
|
+
export declare const DEFAULT_PET_ID = "whale-girl";
|
|
4
|
+
/** Default pet name used only when a manifest carries no displayName. */
|
|
5
|
+
export declare const DEFAULT_PET_NAME = "\u9CB8\u9C7C\u5A18";
|
|
6
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/defaults.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAEhF,8EAA8E;AAC9E,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,uBAAQ,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Shared defaults used by registry selection and persisted-state migration. */
|
|
2
|
+
/** Pet id that legacy single-pet installs and fresh registries resolve to. */
|
|
3
|
+
export const DEFAULT_PET_ID = 'whale-girl';
|
|
4
|
+
/** Default pet name used only when a manifest carries no displayName. */
|
|
5
|
+
export const DEFAULT_PET_NAME = '鲸鱼娘';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dsh-home.d.ts","sourceRoot":"","sources":["../../src/dsh-home.ts"],"names":[],"mappings":"AACA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"dsh-home.d.ts","sourceRoot":"","sources":["../../src/dsh-home.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAMH,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAMzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAUrG;AAED,gEAAgE;AAChE,wBAAgB,OAAO,IAAI,MAAM,CAEhC"}
|
package/lib/types/dsh-home.js
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { isAbsolute, join } from 'node:path';
|
|
9
|
+
import { isAbsolute as posixIsAbsolute, join as posixJoin } from 'node:path/posix';
|
|
9
10
|
/** Expand a leading ~ (or ~user) in a path, platform-style. */
|
|
10
11
|
export function expandHome(path, home = homedir()) {
|
|
12
|
+
const isPosix = home.startsWith('/');
|
|
13
|
+
const j = isPosix ? posixJoin : join;
|
|
11
14
|
if (path === '~')
|
|
12
15
|
return home;
|
|
13
16
|
if (path.startsWith('~/') || path.startsWith('~\\'))
|
|
14
|
-
return
|
|
17
|
+
return j(home, path.slice(2));
|
|
15
18
|
return path;
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
@@ -21,12 +24,15 @@ export function expandHome(path, home = homedir()) {
|
|
|
21
24
|
* @returns the absolute DSH home path.
|
|
22
25
|
*/
|
|
23
26
|
export function resolveDshHome(env = process.env, home = homedir()) {
|
|
27
|
+
const isPosix = home.startsWith('/');
|
|
28
|
+
const j = isPosix ? posixJoin : join;
|
|
29
|
+
const isAbs = isPosix ? posixIsAbsolute : isAbsolute;
|
|
24
30
|
const raw = env.DSH_HOME;
|
|
25
31
|
if (raw !== undefined && raw.trim() !== '') {
|
|
26
32
|
const expanded = expandHome(raw.trim(), home);
|
|
27
|
-
return
|
|
33
|
+
return isAbs(expanded) ? expanded : j(process.cwd(), expanded);
|
|
28
34
|
}
|
|
29
|
-
return
|
|
35
|
+
return j(home, '.dsh');
|
|
30
36
|
}
|
|
31
37
|
/** Resolve the DSH home directory from the live environment. */
|
|
32
38
|
export function dshHome() {
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* {@link ProjectionRuntime} per session and feed events in arrival order.
|
|
6
6
|
*
|
|
7
7
|
* Status copy comes from the chatter voice (big rotating pools, per-tool
|
|
8
|
-
* families, real-argument hints), and
|
|
9
|
-
*
|
|
8
|
+
* families, real-argument hints), and the projection feeds the murmur engine
|
|
9
|
+
* (碎碎念) with the SITUATION — thinking/writing during the stream, the
|
|
10
|
+
* running tool family on tool calls, and the STRUCTURED outcome on
|
|
11
|
+
* tool/result and turn/end — so the pet's inner voice always roughly knows
|
|
12
|
+
* what is going on and never mis-fires on output text. The wall clock is
|
|
10
13
|
* injected by the caller, keeping every projection reproducible.
|
|
11
14
|
* @module @linxin666/dsh-pet/event-projection
|
|
12
15
|
*/
|
|
@@ -22,18 +25,20 @@ export interface ActivityStatusEventLike {
|
|
|
22
25
|
/** Per-session facts needed to project the official event stream. */
|
|
23
26
|
export interface ProjectionRuntime {
|
|
24
27
|
activeTools: Set<string>;
|
|
28
|
+
/** callIds whose tool looked like a test run, marked at tool/call (pet M6). */
|
|
29
|
+
testCalls: Set<string>;
|
|
25
30
|
officialEventsSeen: boolean;
|
|
26
31
|
stepHadFailure: boolean;
|
|
27
32
|
/** Round-robin status copy voice (scene-stable, cadence-rotated). */
|
|
28
33
|
voice: StatusVoice;
|
|
29
|
-
/** Inner-whisper engine fed by the
|
|
34
|
+
/** Inner-whisper engine fed by the projection's situation and outcomes. */
|
|
30
35
|
whispers: WhisperEngine;
|
|
31
36
|
}
|
|
32
37
|
/** One official event projection, optionally carrying a completed turn reward. */
|
|
33
38
|
export interface PetActivityTransition {
|
|
34
39
|
input: PetStateInput;
|
|
35
40
|
completedTurn?: number;
|
|
36
|
-
/** A fresh inner whisper woken by this event
|
|
41
|
+
/** A fresh inner whisper woken by this event, when any. */
|
|
37
42
|
whisper?: string;
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
@@ -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;;;;;;;;;;;;;;GAcG;AAEH,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,CAmHnC"}
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* {@link ProjectionRuntime} per session and feed events in arrival order.
|
|
6
6
|
*
|
|
7
7
|
* Status copy comes from the chatter voice (big rotating pools, per-tool
|
|
8
|
-
* families, real-argument hints), and
|
|
9
|
-
*
|
|
8
|
+
* families, real-argument hints), and the projection feeds the murmur engine
|
|
9
|
+
* (碎碎念) with the SITUATION — thinking/writing during the stream, the
|
|
10
|
+
* running tool family on tool calls, and the STRUCTURED outcome on
|
|
11
|
+
* tool/result and turn/end — so the pet's inner voice always roughly knows
|
|
12
|
+
* what is going on and never mis-fires on output text. The wall clock is
|
|
10
13
|
* injected by the caller, keeping every projection reproducible.
|
|
11
14
|
* @module @linxin666/dsh-pet/event-projection
|
|
12
15
|
*/
|
|
13
|
-
import { StatusVoice, toolArgHint, WhisperEngine } from "./chatter.js";
|
|
16
|
+
import { StatusVoice, toolArgHint, toolCategory, WhisperEngine, whisperCategoryOf, looksLikeTestTool, } from "./chatter.js";
|
|
14
17
|
/**
|
|
15
18
|
* Fresh projection runtime for a newly seen session. The optional voice-pack
|
|
16
19
|
* provider (pet-center M4, issue #677) hands both chatter engines their
|
|
@@ -20,6 +23,7 @@ import { StatusVoice, toolArgHint, WhisperEngine } from "./chatter.js";
|
|
|
20
23
|
export function emptyProjectionRuntime(pools) {
|
|
21
24
|
return {
|
|
22
25
|
activeTools: new Set(),
|
|
26
|
+
testCalls: new Set(),
|
|
23
27
|
officialEventsSeen: false,
|
|
24
28
|
stepHadFailure: false,
|
|
25
29
|
voice: new StatusVoice(pools),
|
|
@@ -44,6 +48,7 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
44
48
|
switch (event.type) {
|
|
45
49
|
case 'turn/start':
|
|
46
50
|
runtime.activeTools.clear();
|
|
51
|
+
runtime.testCalls.clear();
|
|
47
52
|
runtime.stepHadFailure = false;
|
|
48
53
|
return { input: { phase: 'waiting', line: runtime.voice.scene('prepare', nowMs) } };
|
|
49
54
|
case 'step/start':
|
|
@@ -53,14 +58,14 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
53
58
|
case 'assistant/chunk': {
|
|
54
59
|
const { chunk } = event.data;
|
|
55
60
|
if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
|
|
56
|
-
const whisper = runtime.whispers.feed(
|
|
61
|
+
const whisper = runtime.whispers.feed('thinking', nowMs);
|
|
57
62
|
return {
|
|
58
63
|
input: { phase: 'thinking', line: runtime.voice.scene('thinking', nowMs) },
|
|
59
64
|
...(whisper === undefined ? {} : { whisper }),
|
|
60
65
|
};
|
|
61
66
|
}
|
|
62
67
|
if (chunk.type === 'text-delta' && chunk.text.length > 0) {
|
|
63
|
-
const whisper = runtime.whispers.feed(
|
|
68
|
+
const whisper = runtime.whispers.feed('writing', nowMs);
|
|
64
69
|
return {
|
|
65
70
|
input: { phase: 'review', line: runtime.voice.scene('review', nowMs) },
|
|
66
71
|
...(whisper === undefined ? {} : { whisper }),
|
|
@@ -70,40 +75,67 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
70
75
|
}
|
|
71
76
|
case 'assistant/message':
|
|
72
77
|
return { input: { phase: 'review', line: runtime.voice.scene('review', nowMs) } };
|
|
73
|
-
case 'tool/call':
|
|
74
|
-
|
|
78
|
+
case 'tool/call': {
|
|
79
|
+
const callId = String(event.data.callId);
|
|
80
|
+
runtime.activeTools.add(callId);
|
|
81
|
+
// A test-looking call is remembered so its result (the only place the
|
|
82
|
+
// outcome is known) can wake the test-green mood later.
|
|
83
|
+
if (looksLikeTestTool(event.data.name, event.data.arguments))
|
|
84
|
+
runtime.testCalls.add(callId);
|
|
85
|
+
const whisper = runtime.whispers.feed(whisperCategoryOf(toolCategory(event.data.name)), nowMs);
|
|
75
86
|
return {
|
|
76
87
|
input: {
|
|
77
88
|
phase: 'tool',
|
|
78
89
|
line: runtime.voice.tool(event.data.name, displayToolName(event.data.name), toolArgHint(event.data.name, event.data.arguments), nowMs),
|
|
79
90
|
},
|
|
91
|
+
...(whisper === undefined ? {} : { whisper }),
|
|
80
92
|
};
|
|
93
|
+
}
|
|
81
94
|
case 'tool/result': {
|
|
82
95
|
const block = event.data.message.content[0];
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
const callId = String(event.data.message.source.callId);
|
|
97
|
+
const failed = event.data.error !== undefined || block.isError === true;
|
|
98
|
+
const wasTest = runtime.testCalls.delete(callId);
|
|
99
|
+
runtime.activeTools.delete(callId);
|
|
100
|
+
runtime.stepHadFailure ||= failed;
|
|
101
|
+
const whisper = failed
|
|
102
|
+
? runtime.whispers.result('fail', nowMs)
|
|
103
|
+
: wasTest
|
|
104
|
+
? runtime.whispers.result('pass', nowMs)
|
|
105
|
+
: undefined;
|
|
106
|
+
const whisperSpread = whisper === undefined ? {} : { whisper };
|
|
85
107
|
if (runtime.activeTools.size > 0) {
|
|
86
108
|
return {
|
|
87
109
|
input: {
|
|
88
110
|
phase: 'tool',
|
|
89
111
|
line: runtime.voice.toolRemaining(runtime.activeTools.size, nowMs),
|
|
90
112
|
},
|
|
113
|
+
...whisperSpread,
|
|
91
114
|
};
|
|
92
115
|
}
|
|
93
116
|
return runtime.stepHadFailure
|
|
94
|
-
? { input: { phase: 'failed', line: runtime.voice.scene('toolFailed', nowMs) } }
|
|
95
|
-
: { input: { phase: 'thinking', line: runtime.voice.scene('toolResult', nowMs) } };
|
|
117
|
+
? { input: { phase: 'failed', line: runtime.voice.scene('toolFailed', nowMs) }, ...whisperSpread }
|
|
118
|
+
: { input: { phase: 'thinking', line: runtime.voice.scene('toolResult', nowMs) }, ...whisperSpread };
|
|
96
119
|
}
|
|
97
120
|
case 'turn/end': {
|
|
98
121
|
runtime.activeTools.clear();
|
|
122
|
+
runtime.testCalls.clear();
|
|
99
123
|
switch (event.data.reason.kind) {
|
|
100
|
-
case 'completed':
|
|
124
|
+
case 'completed': {
|
|
125
|
+
const whisper = runtime.whispers.result('done', nowMs);
|
|
101
126
|
return {
|
|
102
127
|
input: { phase: 'done', line: runtime.voice.scene('done', nowMs) },
|
|
103
128
|
completedTurn: event.data.turn,
|
|
129
|
+
...(whisper === undefined ? {} : { whisper }),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
case 'error': {
|
|
133
|
+
const whisper = runtime.whispers.result('fail', nowMs);
|
|
134
|
+
return {
|
|
135
|
+
input: { phase: 'failed', line: runtime.voice.scene('failed', nowMs) },
|
|
136
|
+
...(whisper === undefined ? {} : { whisper }),
|
|
104
137
|
};
|
|
105
|
-
|
|
106
|
-
return { input: { phase: 'failed', line: runtime.voice.scene('failed', nowMs) } };
|
|
138
|
+
}
|
|
107
139
|
case 'max-tokens':
|
|
108
140
|
return { input: { phase: 'failed', line: runtime.voice.scene('maxTokens', nowMs) } };
|
|
109
141
|
case 'interrupted':
|