dsh-ntr 0.3.2 → 0.3.4
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.en.md +2 -0
- package/README.md +2 -0
- package/lib/bridge.js +1930 -60
- package/lib/client/activity-pill.js +156 -0
- package/lib/client/branch-action.js +16 -0
- package/lib/client/index.js +44 -4
- package/lib/client/model-seat.js +8 -2
- package/lib/client/store.js +32 -0
- package/lib/client.js +228 -5
- package/lib/client.js.map +1 -1
- package/lib/impl.js +1281 -23
- package/lib/index.js +27 -0
- package/lib/tps.js +32 -0
- package/lib/types/bridge.d.ts +636 -12
- package/lib/types/client/activity-pill.d.ts +81 -0
- package/lib/types/client/branch-action.d.ts +8 -1
- package/lib/types/client/model-seat.d.ts +21 -0
- package/lib/types/client/store.d.ts +10 -0
- package/lib/types/impl.d.ts +61 -6
- package/lib/types/protocol.d.ts +22 -0
- package/lib/types/tps.d.ts +17 -0
- package/package.json +1 -1
- package/src/bridge.ts +2216 -60
- package/src/client/activity-pill.tsx +201 -0
- package/src/client/branch-action.tsx +19 -1
- package/src/client/index.tsx +36 -4
- package/src/client/model-seat.tsx +28 -3
- package/src/client/store.ts +37 -0
- package/src/impl.ts +1268 -27
- package/src/index.ts +25 -0
- package/src/protocol.ts +24 -0
- package/src/tps.ts +40 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The composer dock's `x轮x步 · xx tok/s` pill on an ACP conversation.
|
|
3
|
+
*
|
|
4
|
+
* WHY SHADOW INSTEAD OF A SECOND PILL: the host's `ActivityPill` already states
|
|
5
|
+
* the two things this conversation has — turn/step counts and a whole-session
|
|
6
|
+
* decode speed — in the one place a speed belongs. Only its speed is wrong for
|
|
7
|
+
* ACP: the host divides the tokens a step emitted by `firstTokenTime →
|
|
8
|
+
* completedTime`, and that window contains every tool wait. Rendering a second
|
|
9
|
+
* speed beside it would leave two numbers on screen and no way to tell which
|
|
10
|
+
* is the honest one. So this module takes the host pill's own cell (`list` slot
|
|
11
|
+
* `conversation.composer.dock`, id `activity`) at a lower priority, renders the
|
|
12
|
+
* HOST's component, and hands it a patched `sessionStats` whose decode fields
|
|
13
|
+
* are the plugin's burst aggregate. Style, counts, dialog, aria and locale stay
|
|
14
|
+
* the host's; only the number changes.
|
|
15
|
+
*
|
|
16
|
+
* NON-ACP CONVERSATIONS PASS THROUGH. The shadow is installed only while the
|
|
17
|
+
* gate confirms the staged conversation runs the ACP preset, but a list entry
|
|
18
|
+
* is projected into EVERY session's composer. The wrapper therefore renders the
|
|
19
|
+
* host component with the ORIGINAL projection whenever `acpMode` is not
|
|
20
|
+
* affirmatively true, making the shadow visually indistinguishable from the
|
|
21
|
+
* shipped entry there.
|
|
22
|
+
*
|
|
23
|
+
* FAIL-OPEN IS STRUCTURAL: if the host entry, its inject face, its locale or
|
|
24
|
+
* its `performanceUsage` source is missing, no claim is created and the native
|
|
25
|
+
* pill stays exactly as shipped. If the host component rejects the patched
|
|
26
|
+
* projection, the slot's error boundary retires this entry and the native
|
|
27
|
+
* `activity` entry wins again. No CSS hiding and no DOM patching are involved.
|
|
28
|
+
*/
|
|
29
|
+
import { createElement, useMemo } from 'react';
|
|
30
|
+
import { useUi } from "./acp-ui.js";
|
|
31
|
+
import { createSeatClaim } from "./model-seat.js";
|
|
32
|
+
export const DOCK_SLOT = 'conversation.composer.dock';
|
|
33
|
+
export const ACTIVITY_ENTRY_ID = 'activity';
|
|
34
|
+
/**
|
|
35
|
+
* The aggregate the dock should show: the sum of every ACP session backing this
|
|
36
|
+
* chat (the host pill's counts are whole-chat too), with a live `turn/metrics`
|
|
37
|
+
* entry preferred per session over its persisted row. `undefined` means "no
|
|
38
|
+
* measurable burst yet" — the wrapper then hides the speed instead of showing
|
|
39
|
+
* the host's tool-inclusive number.
|
|
40
|
+
*/
|
|
41
|
+
export function activityBurst(ui, dshSessionId) {
|
|
42
|
+
if (ui.chatSessionsFor !== dshSessionId)
|
|
43
|
+
return undefined;
|
|
44
|
+
let outputTokens = 0;
|
|
45
|
+
let burstMs = 0;
|
|
46
|
+
for (const row of ui.chatSessions) {
|
|
47
|
+
const measured = ui.burstStats[row.sessionId] ?? row.burst;
|
|
48
|
+
if (measured === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
outputTokens += measured.outputTokens;
|
|
51
|
+
burstMs += measured.burstMs;
|
|
52
|
+
}
|
|
53
|
+
return outputTokens > 0 && burstMs > 0 ? { outputTokens, burstMs } : undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Replace ONLY the decode half of the host's session stats.
|
|
57
|
+
*
|
|
58
|
+
* `decodeTokens` and `decodeMs` are the two fields the host's `decodeSpeed`
|
|
59
|
+
* reads (`decodeTokens / (decodeMs / 1000)`), so writing the plugin's aggregate
|
|
60
|
+
* there makes the host's own formatter, dialog row and aria label display the
|
|
61
|
+
* plugin's burst rate. Every other field — turns, steps, LLM/tool/TTFT — stays
|
|
62
|
+
* host-native. With no measurable burst the two decode fields are zeroed: the
|
|
63
|
+
* host then hides the speed (`decodeMs > 0` guard) while keeping the counts.
|
|
64
|
+
*/
|
|
65
|
+
export function patchedActivityStats(native, burst, acp) {
|
|
66
|
+
if (!acp || native === undefined)
|
|
67
|
+
return native;
|
|
68
|
+
if (burst !== undefined && burst.outputTokens > 0 && burst.burstMs > 0) {
|
|
69
|
+
return { ...native, decodeTokens: burst.outputTokens, decodeMs: burst.burstMs };
|
|
70
|
+
}
|
|
71
|
+
return { ...native, decodeTokens: 0, decodeMs: 0 };
|
|
72
|
+
}
|
|
73
|
+
function ActivityPillOccupant({ host: Host, ...props }) {
|
|
74
|
+
const ui = useUi(props.acp);
|
|
75
|
+
const dshSessionId = String(props.sessionId);
|
|
76
|
+
const acpMode = ui.acpMode[dshSessionId] === true;
|
|
77
|
+
const native = props.useProjection('sessionStats');
|
|
78
|
+
const burst = acpMode ? activityBurst(ui, dshSessionId) : undefined;
|
|
79
|
+
const stats = useMemo(() => patchedActivityStats(native, burst, acpMode), [native, burst?.outputTokens, burst?.burstMs, acpMode]);
|
|
80
|
+
// The current host pill reads exactly one projection. Returning `undefined`
|
|
81
|
+
// for another key keeps the wrapper from claiming support it has not been
|
|
82
|
+
// taught; a future host that asks for more will abdicate to the native entry.
|
|
83
|
+
const projection = useMemo(() => (key, selector) => {
|
|
84
|
+
if (key !== 'sessionStats')
|
|
85
|
+
return undefined;
|
|
86
|
+
return selector === undefined ? stats : selector(stats);
|
|
87
|
+
}, [stats]);
|
|
88
|
+
return createElement(Host, {
|
|
89
|
+
useChat: props.useChat,
|
|
90
|
+
useProjection: acpMode ? projection : props.useProjection,
|
|
91
|
+
usePerformanceUsage: props.usePerformanceUsage,
|
|
92
|
+
t: props.t,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function findHostActivity(slots) {
|
|
96
|
+
try {
|
|
97
|
+
if (slots.spec?.(DOCK_SLOT)?.kind !== 'list')
|
|
98
|
+
return undefined;
|
|
99
|
+
const heads = slots.entriesOfSlot?.(DOCK_SLOT) ?? [];
|
|
100
|
+
return heads.find(entry => entry.options?.id === ACTIVITY_ENTRY_ID);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Take the dock's `activity` cell for one ACP-mode spell.
|
|
108
|
+
*
|
|
109
|
+
* Returns `undefined` on any host shape this bundle cannot serve — an older
|
|
110
|
+
* host without the `activity` entry (0.1.5 ships `stats`), a slot kind change,
|
|
111
|
+
* a missing inject/locale, or no `performanceUsage` source. The native pill is
|
|
112
|
+
* then left untouched; that is the honest failure mode, not a hidden control.
|
|
113
|
+
*/
|
|
114
|
+
export function createActivityPillClaim(options) {
|
|
115
|
+
const host = findHostActivity(options.slots);
|
|
116
|
+
if (host === undefined
|
|
117
|
+
|| host.component === undefined
|
|
118
|
+
|| typeof host.inject !== 'function'
|
|
119
|
+
|| typeof host.locale !== 'string'
|
|
120
|
+
|| host.locale === '') {
|
|
121
|
+
options.onDiag?.('activity pill: host entry not available');
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
let performanceUsage;
|
|
125
|
+
try {
|
|
126
|
+
const face = host.inject();
|
|
127
|
+
performanceUsage = face?.hooks?.performanceUsage;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
performanceUsage = undefined;
|
|
131
|
+
}
|
|
132
|
+
if (performanceUsage === undefined) {
|
|
133
|
+
options.onDiag?.('activity pill: host performance hook missing');
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const Host = host.component;
|
|
137
|
+
return createSeatClaim({
|
|
138
|
+
slots: options.slots,
|
|
139
|
+
slotKey: DOCK_SLOT,
|
|
140
|
+
entryId: ACTIVITY_ENTRY_ID,
|
|
141
|
+
order: host.options?.order ?? 0,
|
|
142
|
+
locale: host.locale,
|
|
143
|
+
basePriority: -1,
|
|
144
|
+
occupant: () => (props) => createElement(ActivityPillOccupant, {
|
|
145
|
+
...props,
|
|
146
|
+
host: Host,
|
|
147
|
+
}),
|
|
148
|
+
inject: (sessionId, actions) => ({
|
|
149
|
+
...options.inject(sessionId, actions),
|
|
150
|
+
hooks: { performanceUsage },
|
|
151
|
+
}),
|
|
152
|
+
label: 'activity pill',
|
|
153
|
+
onDiag: options.onDiag,
|
|
154
|
+
maxClaims: options.maxClaims ?? 1,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
@@ -26,6 +26,22 @@ import { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
|
26
26
|
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives';
|
|
27
27
|
import { useUi } from "./acp-ui.js";
|
|
28
28
|
import { forkStateOf } from "./store.js";
|
|
29
|
+
/**
|
|
30
|
+
* Resolves the runtime's `sessions.fork` primitive, binding it to the service
|
|
31
|
+
* instance so that internal `this` accesses (such as `this.list.getSnapshot()`)
|
|
32
|
+
* resolve correctly when called through `face.fork(...)`.
|
|
33
|
+
* Returns `undefined` if the service or `fork` method is absent or not a function.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveHostFork(sessionsService) {
|
|
36
|
+
try {
|
|
37
|
+
const sessions = sessionsService;
|
|
38
|
+
const fork = sessions?.fork;
|
|
39
|
+
return typeof fork === 'function' ? { fork: fork.bind(sessions) } : undefined;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
29
45
|
/**
|
|
30
46
|
* The host's own branch-button icon, picked per host generation. The icon
|
|
31
47
|
* exports MOVED between shipped hosts — desktop 0.2.1-rc.2 has
|
package/lib/client/index.js
CHANGED
|
@@ -14,7 +14,8 @@ import { AcpConversationView } from "./acp-view.js";
|
|
|
14
14
|
import { AcpSettingsDialog } from "./settings-dialog.js";
|
|
15
15
|
import { PermissionDock } from "./permission-dock.js";
|
|
16
16
|
import { ReleasedBanner } from "./released-banner.js";
|
|
17
|
-
import { AcpBranchAction } from "./branch-action.js";
|
|
17
|
+
import { AcpBranchAction, resolveHostFork } from "./branch-action.js";
|
|
18
|
+
import { createActivityPillClaim, DOCK_SLOT } from "./activity-pill.js";
|
|
18
19
|
import { createModelSeatClaim, createPermissionSeatClaim, MODEL_SEAT_KEY, PERMISSION_SEAT_KEY, } from "./model-seat.js";
|
|
19
20
|
import { acpModeFromPresetEvent, ACP_PRESET_ID, rowAgentPreset, stagedIsAcpMode, stagedSessionOf, } from "./acp-ui.js";
|
|
20
21
|
import { createSlashIsolation, slashAcpVerdict } from "./slash-isolation.js";
|
|
@@ -146,8 +147,7 @@ export function apply(ctx) {
|
|
|
146
147
|
*/
|
|
147
148
|
const hostFork = () => {
|
|
148
149
|
try {
|
|
149
|
-
|
|
150
|
-
return typeof fork === 'function' ? { fork } : undefined;
|
|
150
|
+
return resolveHostFork(service('sessions'));
|
|
151
151
|
}
|
|
152
152
|
catch {
|
|
153
153
|
return undefined;
|
|
@@ -375,6 +375,28 @@ export function apply(ctx) {
|
|
|
375
375
|
* available yet.
|
|
376
376
|
*/
|
|
377
377
|
let permissionClaim;
|
|
378
|
+
/**
|
|
379
|
+
* Claim on the dock's `activity` cell (`conversation.composer.dock`, id
|
|
380
|
+
* `activity`), held for the same ACP-mode spell as the two seats. The
|
|
381
|
+
* occupant renders the HOST's ActivityPill with the plugin's burst speed
|
|
382
|
+
* patched into its `sessionStats`; it is a list cell, so the claim is
|
|
383
|
+
* identified by id rather than by the slot's heads[0]. If the host shape is
|
|
384
|
+
* not one this bundle knows, no claim is created and the native pill stays.
|
|
385
|
+
*/
|
|
386
|
+
let activityClaim;
|
|
387
|
+
let activityDiag = 'idle';
|
|
388
|
+
const noteActivityDiag = (line) => {
|
|
389
|
+
if (line === activityDiag)
|
|
390
|
+
return;
|
|
391
|
+
activityDiag = line;
|
|
392
|
+
// Success and the ordinary older-host "no activity entry" are silent; a
|
|
393
|
+
// shape mismatch or a lost registration must leave a breadcrumb. The ACP
|
|
394
|
+
// view has no line for this surface, and a missing speed fix is otherwise
|
|
395
|
+
// invisible.
|
|
396
|
+
if (!/failed|missing|taken|gave up|still held/.test(line))
|
|
397
|
+
return;
|
|
398
|
+
console.warn(`[dsh-ntr] ${line}`);
|
|
399
|
+
};
|
|
378
400
|
/**
|
|
379
401
|
* ACP-only auxiliary entries: the NTR view tab and the composer's context
|
|
380
402
|
* ring. Unlike the two seats these are `list` slots with no visibility
|
|
@@ -474,6 +496,10 @@ export function apply(ctx) {
|
|
|
474
496
|
permissionClaim.release();
|
|
475
497
|
permissionClaim = undefined;
|
|
476
498
|
}
|
|
499
|
+
if (activityClaim !== undefined) {
|
|
500
|
+
activityClaim.release();
|
|
501
|
+
activityClaim = undefined;
|
|
502
|
+
}
|
|
477
503
|
};
|
|
478
504
|
/**
|
|
479
505
|
* Re-ask timer for the staged session, armed while the row is silent and a
|
|
@@ -616,6 +642,16 @@ export function apply(ctx) {
|
|
|
616
642
|
onDiag: (line) => { permissionSeatDiag = `staged ${staged.sessionId} · ${line}`; },
|
|
617
643
|
});
|
|
618
644
|
permissionClaim.sync();
|
|
645
|
+
// The dock's activity pill is a list CELL, not a whole slot: this claim
|
|
646
|
+
// shadows the shipped `activity` entry by id and renders it back with the
|
|
647
|
+
// plugin's burst speed. No host entry / inject / locale -> undefined and
|
|
648
|
+
// the native pill is untouched; the next evaluation retries.
|
|
649
|
+
activityClaim ??= createActivityPillClaim({
|
|
650
|
+
slots: ctx.slots,
|
|
651
|
+
inject: (sessionId) => chatFace(sessionId),
|
|
652
|
+
onDiag: noteActivityDiag,
|
|
653
|
+
});
|
|
654
|
+
activityClaim?.sync();
|
|
619
655
|
// The tab and the occupancy ring have the same lifetime as the claims:
|
|
620
656
|
// they exist only while this staged conversation is an ACP one, and a
|
|
621
657
|
// stand-down disposes them (a standard conversation must not see an NTR
|
|
@@ -743,7 +779,7 @@ export function apply(ctx) {
|
|
|
743
779
|
// retakes the cell only when it is actually holding something that is not
|
|
744
780
|
// ours — so this is evidence-driven, not a poll.
|
|
745
781
|
const stopCell = ctx.on('slots/changed', (...args) => {
|
|
746
|
-
if (args[0] !== MODEL_SEAT_KEY && args[0] !== PERMISSION_SEAT_KEY)
|
|
782
|
+
if (args[0] !== MODEL_SEAT_KEY && args[0] !== PERMISSION_SEAT_KEY && args[0] !== DOCK_SLOT)
|
|
747
783
|
return;
|
|
748
784
|
runGate();
|
|
749
785
|
});
|
|
@@ -771,6 +807,10 @@ export function apply(ctx) {
|
|
|
771
807
|
permissionClaim.release();
|
|
772
808
|
permissionClaim = undefined;
|
|
773
809
|
}
|
|
810
|
+
if (activityClaim !== undefined) {
|
|
811
|
+
activityClaim.release();
|
|
812
|
+
activityClaim = undefined;
|
|
813
|
+
}
|
|
774
814
|
store.noteSeatDiag('gate disposed');
|
|
775
815
|
store.notePermissionSeatDiag('gate disposed');
|
|
776
816
|
};
|
package/lib/client/model-seat.js
CHANGED
|
@@ -123,7 +123,7 @@ export const MAX_SEAT_CLAIMS = 4;
|
|
|
123
123
|
* `entriesOfSlot` and performing bounded retakes if displaced by later registrations.
|
|
124
124
|
*/
|
|
125
125
|
export function createSeatClaim(options) {
|
|
126
|
-
const { slots, slotKey, basePriority, occupant, inject } = options;
|
|
126
|
+
const { slots, slotKey, basePriority, occupant, inject, entryId } = options;
|
|
127
127
|
const label = options.label ?? slotKey;
|
|
128
128
|
const onDiag = options.onDiag;
|
|
129
129
|
const maxClaims = options.maxClaims ?? MAX_SEAT_CLAIMS;
|
|
@@ -156,7 +156,10 @@ export function createSeatClaim(options) {
|
|
|
156
156
|
return { declared: isDeclared, verified: false };
|
|
157
157
|
try {
|
|
158
158
|
const heads = slots.entriesOfSlot(slotKey) ?? [];
|
|
159
|
-
|
|
159
|
+
// A list slot projects one winner per id, so the cell this claim owns is
|
|
160
|
+
// the head WITH that id — not heads[0], which may be a sibling cell.
|
|
161
|
+
const entry = entryId === undefined ? heads[0] : heads.find(head => head.options?.id === entryId);
|
|
162
|
+
return { declared: isDeclared, verified: true, ...entry === undefined ? {} : { entry } };
|
|
160
163
|
}
|
|
161
164
|
catch {
|
|
162
165
|
return { declared: isDeclared, verified: false };
|
|
@@ -176,6 +179,9 @@ export function createSeatClaim(options) {
|
|
|
176
179
|
try {
|
|
177
180
|
installed.push(slots.inject(slotKey, () => slots.register({
|
|
178
181
|
name: slotKey,
|
|
182
|
+
...(entryId === undefined ? {} : { id: entryId }),
|
|
183
|
+
...(options.order === undefined ? {} : { order: options.order }),
|
|
184
|
+
...(options.locale === undefined ? {} : { locale: options.locale }),
|
|
179
185
|
priority: rank,
|
|
180
186
|
...(inject ? { inject } : {}),
|
|
181
187
|
}, component)));
|
package/lib/client/store.js
CHANGED
|
@@ -379,6 +379,7 @@ export class AcpStore {
|
|
|
379
379
|
probingModels: {},
|
|
380
380
|
probeErrors: {},
|
|
381
381
|
chatSessions: [],
|
|
382
|
+
burstStats: {},
|
|
382
383
|
tagHalo: { ...DEFAULT_TAG_HALO },
|
|
383
384
|
approvalDeadlines: undefined,
|
|
384
385
|
idleRelease: undefined,
|
|
@@ -1920,11 +1921,20 @@ export class AcpStore {
|
|
|
1920
1921
|
// entry would keep reading as a live session (the agent tag's halo
|
|
1921
1922
|
// breathes on `!released`) until some future turn re-reads the list.
|
|
1922
1923
|
const chatSessions = this.state.chatSessions.filter(s => s.sessionId !== event.sessionId);
|
|
1924
|
+
// The burst aggregate belongs to this ACP session only; another
|
|
1925
|
+
// session's entry must survive. Only patch when there was an entry.
|
|
1926
|
+
const hadBurst = this.state.burstStats[event.sessionId] !== undefined;
|
|
1927
|
+
let burstStats = this.state.burstStats;
|
|
1928
|
+
if (hadBurst) {
|
|
1929
|
+
burstStats = { ...burstStats };
|
|
1930
|
+
delete burstStats[event.sessionId];
|
|
1931
|
+
}
|
|
1923
1932
|
this.mutate({
|
|
1924
1933
|
sessions,
|
|
1925
1934
|
activeSessionId: active,
|
|
1926
1935
|
chatTranscripts: closedTranscripts,
|
|
1927
1936
|
...(chatSessions.length !== this.state.chatSessions.length ? { chatSessions } : {}),
|
|
1937
|
+
...(hadBurst ? { burstStats } : {}),
|
|
1928
1938
|
permissions: this.state.permissions.filter(p => p.sessionId !== event.sessionId),
|
|
1929
1939
|
...(event.reason === 'agent-died'
|
|
1930
1940
|
? this.logPatch('error', this.locale('会话因 agent 进程退出而终止', 'session ended: agent process died'), closingSession?.agentId)
|
|
@@ -2101,6 +2111,28 @@ export class AcpStore {
|
|
|
2101
2111
|
});
|
|
2102
2112
|
return Promise.resolve();
|
|
2103
2113
|
}
|
|
2114
|
+
case 'turn/metrics': {
|
|
2115
|
+
// Plugin-side burst aggregate for one ACP session. A late LIVE frame
|
|
2116
|
+
// must not regress a turn this page already saw finalized (frames can
|
|
2117
|
+
// land out of order across a stream reconnect); any other frame writes
|
|
2118
|
+
// the entry. Blocks / sessions stay untouched — this is a metric.
|
|
2119
|
+
const prior = this.state.burstStats[event.sessionId];
|
|
2120
|
+
if (event.final === false && prior !== undefined && prior.final === true && prior.turnId === event.turnId) {
|
|
2121
|
+
return Promise.resolve();
|
|
2122
|
+
}
|
|
2123
|
+
this.mutate({
|
|
2124
|
+
burstStats: {
|
|
2125
|
+
...this.state.burstStats,
|
|
2126
|
+
[event.sessionId]: {
|
|
2127
|
+
outputTokens: event.outputTokens,
|
|
2128
|
+
burstMs: event.burstMs,
|
|
2129
|
+
turnId: event.turnId,
|
|
2130
|
+
final: event.final,
|
|
2131
|
+
},
|
|
2132
|
+
},
|
|
2133
|
+
});
|
|
2134
|
+
return Promise.resolve();
|
|
2135
|
+
}
|
|
2104
2136
|
case 'turn/start': {
|
|
2105
2137
|
this.openTurns.add(event.sessionId);
|
|
2106
2138
|
this.markWatchedRunning(event.sessionId, true);
|