agl 22.0.3 → 23.0.0
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/changelog.md +20 -0
- package/dist_serve/bundle.js +2351 -2325
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.authstore.d.ts +1 -0
- package/dist_ts/classes.authstore.js +30 -1
- package/dist_ts/classes.controller.d.ts +2 -0
- package/dist_ts/classes.controller.js +520 -25
- package/dist_ts/classes.flexservice.d.ts +5 -0
- package/dist_ts/classes.flexservice.js +131 -4
- package/dist_ts/classes.opencodeclient.d.ts +2 -0
- package/dist_ts/classes.opencodeclient.js +23 -1
- package/dist_ts/interfaces.flexipc.d.ts +18 -1
- package/dist_ts/interfaces.flexipc.js +42 -8
- package/dist_ts_interfaces/interfaces.d.ts +33 -2
- package/dist_ts_interfaces/interfaces.js +5 -2
- package/package.json +4 -4
- package/readme.md +6 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.authstore.ts +44 -0
- package/ts/classes.controller.ts +617 -26
- package/ts/classes.flexservice.ts +132 -2
- package/ts/classes.opencodeclient.ts +19 -0
- package/ts/interfaces.flexipc.ts +65 -7
- package/ts_interfaces/interfaces.ts +36 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/classes.controllersocketclient.ts +113 -12
- package/ts_web/elements.harnesscontrollerapp.ts +1282 -170
|
@@ -128,6 +128,33 @@ interface IDetailLoadOwnership {
|
|
|
128
128
|
messageStreamEpoch: number;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
interface ILiveMessageBaseline<
|
|
132
|
+
TUpdate extends interfaces.IControllerReasoningUpdate | interfaces.IControllerTextUpdate,
|
|
133
|
+
> {
|
|
134
|
+
update: TUpdate;
|
|
135
|
+
textUtf8Bytes: number;
|
|
136
|
+
bytes: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface ICanonicalReasoningTarget {
|
|
140
|
+
message: plugins.deesCatalog.IHarnessMessage;
|
|
141
|
+
part: plugins.deesCatalog.IHarnessReasoningPart;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface ICanonicalChatProjection {
|
|
145
|
+
key: string;
|
|
146
|
+
sourceDetail: IControllerSessionRenderDetail | undefined;
|
|
147
|
+
messages: plugins.deesCatalog.IHarnessMessage[];
|
|
148
|
+
messagesById: Map<string, plugins.deesCatalog.IHarnessMessage>;
|
|
149
|
+
messagesByOrder: Map<string, plugins.deesCatalog.IHarnessMessage>;
|
|
150
|
+
reasoningById: Map<string, ICanonicalReasoningTarget>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface ILiveMessageDeltaBlock {
|
|
154
|
+
sessionId: TBrowserSessionId;
|
|
155
|
+
streamEpoch: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
131
158
|
interface ISlashCatalogOwnership {
|
|
132
159
|
key: string;
|
|
133
160
|
projectId: string;
|
|
@@ -282,6 +309,57 @@ const controllerJsonValuesEqual = (leftArg: unknown, rightArg: unknown): boolean
|
|
|
282
309
|
));
|
|
283
310
|
};
|
|
284
311
|
|
|
312
|
+
const reconcileKeyedJsonArray = <TValue>(
|
|
313
|
+
currentArg: readonly TValue[],
|
|
314
|
+
nextArg: readonly TValue[],
|
|
315
|
+
keyForArg: (valueArg: TValue, indexArg: number) => string,
|
|
316
|
+
): TValue[] => {
|
|
317
|
+
const currentByKey = new Map(
|
|
318
|
+
currentArg.map((value, index) => [keyForArg(value, index), value]),
|
|
319
|
+
);
|
|
320
|
+
const seenKeys = new Set<string>();
|
|
321
|
+
let unchanged = currentArg.length === nextArg.length;
|
|
322
|
+
const reconciled = nextArg.map((nextValue, index) => {
|
|
323
|
+
const key = keyForArg(nextValue, index);
|
|
324
|
+
const currentValue = seenKeys.has(key) ? undefined : currentByKey.get(key);
|
|
325
|
+
seenKeys.add(key);
|
|
326
|
+
const value = currentValue !== undefined && controllerJsonValuesEqual(currentValue, nextValue)
|
|
327
|
+
? currentValue
|
|
328
|
+
: nextValue;
|
|
329
|
+
unchanged &&= value === currentArg[index];
|
|
330
|
+
return value;
|
|
331
|
+
});
|
|
332
|
+
return unchanged ? currentArg as TValue[] : reconciled;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const browserTextEncoder = new TextEncoder();
|
|
336
|
+
|
|
337
|
+
const controllerTextAfterAppendUtf8Bytes = (
|
|
338
|
+
textArg: string,
|
|
339
|
+
textUtf8BytesArg: number,
|
|
340
|
+
deltaArg: string,
|
|
341
|
+
): number | undefined => {
|
|
342
|
+
const previousCodeUnit = textArg.charCodeAt(textArg.length - 1);
|
|
343
|
+
const nextCodeUnit = deltaArg.charCodeAt(0);
|
|
344
|
+
// Separately encoded surrogate halves cost six bytes; the joined pair costs four.
|
|
345
|
+
const boundaryCorrection = textArg.length > 0
|
|
346
|
+
&& deltaArg.length > 0
|
|
347
|
+
&& previousCodeUnit >= 0xD800
|
|
348
|
+
&& previousCodeUnit <= 0xDBFF
|
|
349
|
+
&& nextCodeUnit >= 0xDC00
|
|
350
|
+
&& nextCodeUnit <= 0xDFFF
|
|
351
|
+
? -2
|
|
352
|
+
: 0;
|
|
353
|
+
const result = textUtf8BytesArg
|
|
354
|
+
+ browserTextEncoder.encode(deltaArg).byteLength
|
|
355
|
+
+ boundaryCorrection;
|
|
356
|
+
return Number.isSafeInteger(result) && result >= 0 ? result : undefined;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const transcriptOrderKey = (orderArg: interfaces.IControllerTranscriptOrder): string => (
|
|
360
|
+
`${orderArg.messageIndex}\0${orderArg.partIndex}`
|
|
361
|
+
);
|
|
362
|
+
|
|
285
363
|
const controllerNormalizeBrowserAddress = (addressArg: string): string => {
|
|
286
364
|
const address = addressArg.trim();
|
|
287
365
|
if (!address) throw new Error('Enter a browser address.');
|
|
@@ -628,6 +706,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
628
706
|
private highCpuStartedAt: number | undefined;
|
|
629
707
|
private lastCpuSampleAt: number | undefined;
|
|
630
708
|
private refreshPending = false;
|
|
709
|
+
private refreshRelatedPending = false;
|
|
631
710
|
private projectsRequestId = 0;
|
|
632
711
|
private modelCatalogRequestId = 0;
|
|
633
712
|
private layoutRequestId = 0;
|
|
@@ -757,13 +836,30 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
757
836
|
private readonly liveToolOverlays = new Map<string, ILiveToolOverlay>();
|
|
758
837
|
private readonly liveReasoningUpdates = new Map<string, interfaces.IControllerReasoningUpdate>();
|
|
759
838
|
private readonly liveTextUpdates = new Map<string, interfaces.IControllerTextUpdate>();
|
|
839
|
+
private readonly liveReasoningBaselines = new Map<
|
|
840
|
+
string,
|
|
841
|
+
ILiveMessageBaseline<interfaces.IControllerReasoningUpdate>
|
|
842
|
+
>();
|
|
843
|
+
private readonly liveTextBaselines = new Map<
|
|
844
|
+
string,
|
|
845
|
+
ILiveMessageBaseline<interfaces.IControllerTextUpdate>
|
|
846
|
+
>();
|
|
847
|
+
private readonly liveReasoningTextUtf8Bytes = new Map<string, number>();
|
|
848
|
+
private readonly liveTextTextUtf8Bytes = new Map<string, number>();
|
|
849
|
+
private readonly liveReasoningStoredBytes = new Map<string, number>();
|
|
850
|
+
private readonly liveTextStoredBytes = new Map<string, number>();
|
|
760
851
|
private readonly liveToolHydrationFloors = new Map<string, ILiveToolHydrationFloor>();
|
|
761
852
|
private readonly liveMessageHydrationFloors = new Map<string, ILiveMessageHydrationFloor>();
|
|
853
|
+
private readonly liveMessageDeltaBlocks = new Map<string, ILiveMessageDeltaBlock>();
|
|
762
854
|
private readonly liveHistoryBarriers = new Map<string, number>();
|
|
763
855
|
private liveToolOverlayBytes = 0;
|
|
764
856
|
private liveMessageOverlayBytes = 0;
|
|
765
857
|
private readonly liveToolStreamEpochs = new Map<TBrowserSessionHarnessId, number>();
|
|
766
858
|
private readonly liveMessageStreamEpochs = new Map<TBrowserSessionHarnessId, number>();
|
|
859
|
+
private canonicalChatProjection: ICanonicalChatProjection | undefined;
|
|
860
|
+
private canonicalChatProjectionDirty = false;
|
|
861
|
+
private canonicalTimelineRefreshPending = false;
|
|
862
|
+
private readonly canonicalMessageRefreshIds = new Set<string>();
|
|
767
863
|
private readonly subtaskPreviewEntries = new Map<string, ISubtaskPreviewEntry>();
|
|
768
864
|
private subtaskPreviewGeneration = 0;
|
|
769
865
|
private subtaskPreviewHydrationQueue: string[] = [];
|
|
@@ -1824,8 +1920,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
1824
1920
|
padding: 0 var(--dees-spacing-sm);
|
|
1825
1921
|
}
|
|
1826
1922
|
|
|
1827
|
-
.brandSubtitle
|
|
1828
|
-
.headerActions .refreshAction {
|
|
1923
|
+
.brandSubtitle {
|
|
1829
1924
|
display: none;
|
|
1830
1925
|
}
|
|
1831
1926
|
|
|
@@ -2016,6 +2111,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2016
2111
|
this.draftSessionDetailReadyId = undefined;
|
|
2017
2112
|
this.authenticated = false;
|
|
2018
2113
|
this.refreshPending = false;
|
|
2114
|
+
this.refreshRelatedPending = false;
|
|
2019
2115
|
this.detailRefreshPending = false;
|
|
2020
2116
|
this.projects = [];
|
|
2021
2117
|
this.creatingProject = false;
|
|
@@ -2843,12 +2939,13 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2843
2939
|
}
|
|
2844
2940
|
}
|
|
2845
2941
|
|
|
2846
|
-
private refreshSessions(): Promise<void> {
|
|
2942
|
+
private refreshSessions(includeRelatedArg = true): Promise<void> {
|
|
2847
2943
|
if (!this.authenticated || !this.socketClient.isConnected) {
|
|
2848
2944
|
return Promise.resolve();
|
|
2849
2945
|
}
|
|
2850
2946
|
|
|
2851
2947
|
this.refreshPending = true;
|
|
2948
|
+
this.refreshRelatedPending ||= includeRelatedArg;
|
|
2852
2949
|
if (!this.refreshInFlight) {
|
|
2853
2950
|
this.refreshInFlight = this.runRefreshQueue();
|
|
2854
2951
|
}
|
|
@@ -2930,8 +3027,16 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2930
3027
|
if (harnessIdArg === undefined) {
|
|
2931
3028
|
this.liveReasoningUpdates.clear();
|
|
2932
3029
|
this.liveTextUpdates.clear();
|
|
3030
|
+
this.liveReasoningBaselines.clear();
|
|
3031
|
+
this.liveTextBaselines.clear();
|
|
3032
|
+
this.liveReasoningTextUtf8Bytes.clear();
|
|
3033
|
+
this.liveTextTextUtf8Bytes.clear();
|
|
3034
|
+
this.liveReasoningStoredBytes.clear();
|
|
3035
|
+
this.liveTextStoredBytes.clear();
|
|
2933
3036
|
this.liveMessageHydrationFloors.clear();
|
|
3037
|
+
this.liveMessageDeltaBlocks.clear();
|
|
2934
3038
|
this.liveMessageOverlayBytes = 0;
|
|
3039
|
+
this.canonicalChatProjectionDirty = true;
|
|
2935
3040
|
return;
|
|
2936
3041
|
}
|
|
2937
3042
|
for (const [key, floor] of this.liveMessageHydrationFloors) {
|
|
@@ -2943,6 +3048,18 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2943
3048
|
for (const [key, update] of this.liveTextUpdates) {
|
|
2944
3049
|
if (update.sessionId.harnessId === harnessIdArg) this.deleteLiveTextUpdate(key);
|
|
2945
3050
|
}
|
|
3051
|
+
for (const [key, baseline] of this.liveReasoningBaselines) {
|
|
3052
|
+
if (baseline.update.sessionId.harnessId === harnessIdArg) {
|
|
3053
|
+
this.deleteLiveReasoningBaseline(key);
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
for (const [key, baseline] of this.liveTextBaselines) {
|
|
3057
|
+
if (baseline.update.sessionId.harnessId === harnessIdArg) this.deleteLiveTextBaseline(key);
|
|
3058
|
+
}
|
|
3059
|
+
for (const [key, block] of this.liveMessageDeltaBlocks) {
|
|
3060
|
+
if (block.sessionId.harnessId === harnessIdArg) this.liveMessageDeltaBlocks.delete(key);
|
|
3061
|
+
}
|
|
3062
|
+
this.canonicalChatProjectionDirty = true;
|
|
2946
3063
|
}
|
|
2947
3064
|
|
|
2948
3065
|
private clearLiveToolSession(
|
|
@@ -2967,21 +3084,38 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2967
3084
|
&& controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
2968
3085
|
) this.deleteLiveTextUpdate(key);
|
|
2969
3086
|
}
|
|
3087
|
+
for (const [key, baseline] of this.liveReasoningBaselines) {
|
|
3088
|
+
if (
|
|
3089
|
+
key.startsWith(`${projectIdArg}\0`)
|
|
3090
|
+
&& controllerRuntimeIdsEqual(baseline.update.sessionId, sessionIdArg)
|
|
3091
|
+
) this.deleteLiveReasoningBaseline(key);
|
|
3092
|
+
}
|
|
3093
|
+
for (const [key, baseline] of this.liveTextBaselines) {
|
|
3094
|
+
if (
|
|
3095
|
+
key.startsWith(`${projectIdArg}\0`)
|
|
3096
|
+
&& controllerRuntimeIdsEqual(baseline.update.sessionId, sessionIdArg)
|
|
3097
|
+
) this.deleteLiveTextBaseline(key);
|
|
3098
|
+
}
|
|
3099
|
+
this.canonicalChatProjectionDirty = true;
|
|
2970
3100
|
// Keep both hydration floors: they continue fencing snapshots already
|
|
2971
3101
|
// represented by the last authoritative detail until its replacement lands.
|
|
2972
3102
|
}
|
|
2973
3103
|
|
|
2974
3104
|
private liveMessageUpdateBytes(updateArg: unknown): number {
|
|
2975
|
-
return
|
|
3105
|
+
return browserTextEncoder.encode(JSON.stringify(updateArg)).byteLength;
|
|
2976
3106
|
}
|
|
2977
3107
|
|
|
2978
3108
|
private deleteLiveReasoningUpdate(keyArg: string): void {
|
|
2979
3109
|
const update = this.liveReasoningUpdates.get(keyArg);
|
|
2980
3110
|
if (!update) return;
|
|
2981
3111
|
this.liveReasoningUpdates.delete(keyArg);
|
|
3112
|
+
this.liveReasoningTextUtf8Bytes.delete(keyArg);
|
|
3113
|
+
const bytes = this.liveReasoningStoredBytes.get(keyArg)
|
|
3114
|
+
?? this.liveMessageUpdateBytes(update);
|
|
3115
|
+
this.liveReasoningStoredBytes.delete(keyArg);
|
|
2982
3116
|
this.liveMessageOverlayBytes = Math.max(
|
|
2983
3117
|
0,
|
|
2984
|
-
this.liveMessageOverlayBytes -
|
|
3118
|
+
this.liveMessageOverlayBytes - bytes,
|
|
2985
3119
|
);
|
|
2986
3120
|
}
|
|
2987
3121
|
|
|
@@ -2989,29 +3123,61 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2989
3123
|
const update = this.liveTextUpdates.get(keyArg);
|
|
2990
3124
|
if (!update) return;
|
|
2991
3125
|
this.liveTextUpdates.delete(keyArg);
|
|
3126
|
+
this.liveTextTextUtf8Bytes.delete(keyArg);
|
|
3127
|
+
const bytes = this.liveTextStoredBytes.get(keyArg)
|
|
3128
|
+
?? this.liveMessageUpdateBytes(update);
|
|
3129
|
+
this.liveTextStoredBytes.delete(keyArg);
|
|
2992
3130
|
this.liveMessageOverlayBytes = Math.max(
|
|
2993
3131
|
0,
|
|
2994
|
-
this.liveMessageOverlayBytes -
|
|
3132
|
+
this.liveMessageOverlayBytes - bytes,
|
|
2995
3133
|
);
|
|
2996
3134
|
}
|
|
2997
3135
|
|
|
3136
|
+
private deleteLiveReasoningBaseline(keyArg: string): void {
|
|
3137
|
+
const baseline = this.liveReasoningBaselines.get(keyArg);
|
|
3138
|
+
if (!baseline) return;
|
|
3139
|
+
this.liveReasoningBaselines.delete(keyArg);
|
|
3140
|
+
this.liveMessageOverlayBytes = Math.max(0, this.liveMessageOverlayBytes - baseline.bytes);
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
private deleteLiveTextBaseline(keyArg: string): void {
|
|
3144
|
+
const baseline = this.liveTextBaselines.get(keyArg);
|
|
3145
|
+
if (!baseline) return;
|
|
3146
|
+
this.liveTextBaselines.delete(keyArg);
|
|
3147
|
+
this.liveMessageOverlayBytes = Math.max(0, this.liveMessageOverlayBytes - baseline.bytes);
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
private liveMessageEntryCount(): number {
|
|
3151
|
+
return this.liveReasoningUpdates.size
|
|
3152
|
+
+ this.liveTextUpdates.size
|
|
3153
|
+
+ this.liveReasoningBaselines.size
|
|
3154
|
+
+ this.liveTextBaselines.size;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
2998
3157
|
private storeLiveReasoningUpdate(
|
|
2999
3158
|
keyArg: string,
|
|
3000
3159
|
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3001
3160
|
): boolean {
|
|
3002
3161
|
const bytes = this.liveMessageUpdateBytes(updateArg);
|
|
3003
3162
|
const existing = this.liveReasoningUpdates.get(keyArg);
|
|
3004
|
-
const
|
|
3163
|
+
const baseline = this.liveReasoningBaselines.get(keyArg);
|
|
3164
|
+
const existingBytes = existing
|
|
3165
|
+
? this.liveReasoningStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(existing)
|
|
3166
|
+
: baseline?.bytes ?? 0;
|
|
3005
3167
|
if (
|
|
3006
|
-
(!existing &&
|
|
3168
|
+
(!existing && !baseline && this.liveMessageEntryCount() >= maxLiveToolOverlayEntries)
|
|
3007
3169
|
|| this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes
|
|
3008
3170
|
) {
|
|
3009
|
-
this.clearLiveMessageState();
|
|
3010
|
-
this.refreshSessionsNow();
|
|
3011
3171
|
return false;
|
|
3012
3172
|
}
|
|
3013
3173
|
if (existing) this.deleteLiveReasoningUpdate(keyArg);
|
|
3174
|
+
if (baseline) this.deleteLiveReasoningBaseline(keyArg);
|
|
3014
3175
|
this.liveReasoningUpdates.set(keyArg, updateArg);
|
|
3176
|
+
this.liveReasoningTextUtf8Bytes.set(
|
|
3177
|
+
keyArg,
|
|
3178
|
+
browserTextEncoder.encode(updateArg.text).byteLength,
|
|
3179
|
+
);
|
|
3180
|
+
this.liveReasoningStoredBytes.set(keyArg, bytes);
|
|
3015
3181
|
this.liveMessageOverlayBytes += bytes;
|
|
3016
3182
|
return true;
|
|
3017
3183
|
}
|
|
@@ -3022,21 +3188,168 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3022
3188
|
): boolean {
|
|
3023
3189
|
const bytes = this.liveMessageUpdateBytes(updateArg);
|
|
3024
3190
|
const existing = this.liveTextUpdates.get(keyArg);
|
|
3025
|
-
const
|
|
3191
|
+
const baseline = this.liveTextBaselines.get(keyArg);
|
|
3192
|
+
const existingBytes = existing
|
|
3193
|
+
? this.liveTextStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(existing)
|
|
3194
|
+
: baseline?.bytes ?? 0;
|
|
3026
3195
|
if (
|
|
3027
|
-
(!existing &&
|
|
3196
|
+
(!existing && !baseline && this.liveMessageEntryCount() >= maxLiveToolOverlayEntries)
|
|
3028
3197
|
|| this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes
|
|
3029
3198
|
) {
|
|
3030
|
-
this.clearLiveMessageState();
|
|
3031
|
-
this.refreshSessionsNow();
|
|
3032
3199
|
return false;
|
|
3033
3200
|
}
|
|
3034
3201
|
if (existing) this.deleteLiveTextUpdate(keyArg);
|
|
3202
|
+
if (baseline) this.deleteLiveTextBaseline(keyArg);
|
|
3035
3203
|
this.liveTextUpdates.set(keyArg, updateArg);
|
|
3204
|
+
this.liveTextTextUtf8Bytes.set(keyArg, browserTextEncoder.encode(updateArg.text).byteLength);
|
|
3205
|
+
this.liveTextStoredBytes.set(keyArg, bytes);
|
|
3036
3206
|
this.liveMessageOverlayBytes += bytes;
|
|
3037
3207
|
return true;
|
|
3038
3208
|
}
|
|
3039
3209
|
|
|
3210
|
+
private retireLiveReasoningUpdateToBaseline(
|
|
3211
|
+
keyArg: string,
|
|
3212
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3213
|
+
textArg: string,
|
|
3214
|
+
cursorArg: interfaces.IControllerMessageStreamCursor,
|
|
3215
|
+
): void {
|
|
3216
|
+
const update = { ...updateArg, text: textArg, revision: cursorArg.revision };
|
|
3217
|
+
const bytes = this.liveMessageUpdateBytes(update);
|
|
3218
|
+
const existingBytes = this.liveReasoningStoredBytes.get(keyArg)
|
|
3219
|
+
?? this.liveReasoningBaselines.get(keyArg)?.bytes
|
|
3220
|
+
?? this.liveMessageUpdateBytes(updateArg);
|
|
3221
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) {
|
|
3222
|
+
this.deleteLiveReasoningUpdate(keyArg);
|
|
3223
|
+
this.deleteLiveReasoningBaseline(keyArg);
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
this.deleteLiveReasoningUpdate(keyArg);
|
|
3227
|
+
this.deleteLiveReasoningBaseline(keyArg);
|
|
3228
|
+
this.liveReasoningBaselines.set(keyArg, {
|
|
3229
|
+
update,
|
|
3230
|
+
textUtf8Bytes: browserTextEncoder.encode(textArg).byteLength,
|
|
3231
|
+
bytes,
|
|
3232
|
+
});
|
|
3233
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
private retireLiveTextUpdateToBaseline(
|
|
3237
|
+
keyArg: string,
|
|
3238
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
3239
|
+
textArg: string,
|
|
3240
|
+
cursorArg: interfaces.IControllerMessageStreamCursor,
|
|
3241
|
+
): void {
|
|
3242
|
+
const update = { ...updateArg, text: textArg, revision: cursorArg.revision };
|
|
3243
|
+
const bytes = this.liveMessageUpdateBytes(update);
|
|
3244
|
+
const existingBytes = this.liveTextStoredBytes.get(keyArg)
|
|
3245
|
+
?? this.liveTextBaselines.get(keyArg)?.bytes
|
|
3246
|
+
?? this.liveMessageUpdateBytes(updateArg);
|
|
3247
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) {
|
|
3248
|
+
this.deleteLiveTextUpdate(keyArg);
|
|
3249
|
+
this.deleteLiveTextBaseline(keyArg);
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
this.deleteLiveTextUpdate(keyArg);
|
|
3253
|
+
this.deleteLiveTextBaseline(keyArg);
|
|
3254
|
+
this.liveTextBaselines.set(keyArg, {
|
|
3255
|
+
update,
|
|
3256
|
+
textUtf8Bytes: browserTextEncoder.encode(textArg).byteLength,
|
|
3257
|
+
bytes,
|
|
3258
|
+
});
|
|
3259
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
private storeLiveReasoningDeltaState(
|
|
3263
|
+
keyArg: string,
|
|
3264
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3265
|
+
textUtf8BytesArg: number,
|
|
3266
|
+
deltaArg: string,
|
|
3267
|
+
): boolean {
|
|
3268
|
+
const active = this.liveReasoningUpdates.get(keyArg);
|
|
3269
|
+
const baseline = this.liveReasoningBaselines.get(keyArg);
|
|
3270
|
+
const existingBytes = active
|
|
3271
|
+
? this.liveReasoningStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(active)
|
|
3272
|
+
: baseline?.bytes ?? 0;
|
|
3273
|
+
const bytes = existingBytes + Math.max(
|
|
3274
|
+
browserTextEncoder.encode(deltaArg).byteLength,
|
|
3275
|
+
deltaArg.length * 6,
|
|
3276
|
+
);
|
|
3277
|
+
if (!active && !baseline) return false;
|
|
3278
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) return false;
|
|
3279
|
+
this.deleteLiveReasoningUpdate(keyArg);
|
|
3280
|
+
this.deleteLiveReasoningBaseline(keyArg);
|
|
3281
|
+
this.liveReasoningUpdates.set(keyArg, updateArg);
|
|
3282
|
+
this.liveReasoningTextUtf8Bytes.set(keyArg, textUtf8BytesArg);
|
|
3283
|
+
this.liveReasoningStoredBytes.set(keyArg, bytes);
|
|
3284
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3285
|
+
return true;
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
private storeLiveTextDeltaState(
|
|
3289
|
+
keyArg: string,
|
|
3290
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
3291
|
+
textUtf8BytesArg: number,
|
|
3292
|
+
deltaArg: string,
|
|
3293
|
+
): boolean {
|
|
3294
|
+
const active = this.liveTextUpdates.get(keyArg);
|
|
3295
|
+
const baseline = this.liveTextBaselines.get(keyArg);
|
|
3296
|
+
const existingBytes = active
|
|
3297
|
+
? this.liveTextStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(active)
|
|
3298
|
+
: baseline?.bytes ?? 0;
|
|
3299
|
+
const bytes = existingBytes + Math.max(
|
|
3300
|
+
browserTextEncoder.encode(deltaArg).byteLength,
|
|
3301
|
+
deltaArg.length * 6,
|
|
3302
|
+
);
|
|
3303
|
+
if (!active && !baseline) return false;
|
|
3304
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) return false;
|
|
3305
|
+
this.deleteLiveTextUpdate(keyArg);
|
|
3306
|
+
this.deleteLiveTextBaseline(keyArg);
|
|
3307
|
+
this.liveTextUpdates.set(keyArg, updateArg);
|
|
3308
|
+
this.liveTextTextUtf8Bytes.set(keyArg, textUtf8BytesArg);
|
|
3309
|
+
this.liveTextStoredBytes.set(keyArg, bytes);
|
|
3310
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3311
|
+
return true;
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
private liveMessageUpdateKey(
|
|
3315
|
+
projectIdArg: string,
|
|
3316
|
+
updateArg: interfaces.IControllerReasoningUpdate
|
|
3317
|
+
| interfaces.IControllerTextUpdate
|
|
3318
|
+
| interfaces.IControllerReasoningDelta
|
|
3319
|
+
| interfaces.IControllerTextDelta,
|
|
3320
|
+
): string {
|
|
3321
|
+
return `${projectIdArg}\0${controllerRuntimeIdToUiKey(updateArg.sessionId)}\0${controllerRuntimeIdToUiKey(updateArg.partId)}`;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
private blockLiveMessageDeltas(
|
|
3325
|
+
projectIdArg: string,
|
|
3326
|
+
sessionIdArg: TBrowserSessionId,
|
|
3327
|
+
streamEpochArg: number,
|
|
3328
|
+
refreshArg = true,
|
|
3329
|
+
): void {
|
|
3330
|
+
const sessionKey = this.sessionOperationKey(projectIdArg, sessionIdArg);
|
|
3331
|
+
if (this.liveMessageDeltaBlocks.get(sessionKey)?.streamEpoch === streamEpochArg) return;
|
|
3332
|
+
this.liveMessageDeltaBlocks.set(sessionKey, {
|
|
3333
|
+
sessionId: sessionIdArg,
|
|
3334
|
+
streamEpoch: streamEpochArg,
|
|
3335
|
+
});
|
|
3336
|
+
if (
|
|
3337
|
+
refreshArg
|
|
3338
|
+
&& projectIdArg === this.selectedProjectId
|
|
3339
|
+
&& controllerRuntimeIdsEqual(sessionIdArg, this.selectedSessionId)
|
|
3340
|
+
) void this.refreshSelectedSessionDetail();
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
private liveMessageDeltasBlocked(
|
|
3344
|
+
projectIdArg: string,
|
|
3345
|
+
sessionIdArg: TBrowserSessionId,
|
|
3346
|
+
streamEpochArg: number,
|
|
3347
|
+
): boolean {
|
|
3348
|
+
return this.liveMessageDeltaBlocks.get(
|
|
3349
|
+
this.sessionOperationKey(projectIdArg, sessionIdArg),
|
|
3350
|
+
)?.streamEpoch === streamEpochArg;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3040
3353
|
private acceptLiveToolEpoch(
|
|
3041
3354
|
harnessIdArg: TBrowserSessionHarnessId,
|
|
3042
3355
|
epochArg: number,
|
|
@@ -3067,6 +3380,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3067
3380
|
harnessIdArg: TBrowserSessionHarnessId,
|
|
3068
3381
|
epochArg: number,
|
|
3069
3382
|
handleBarrierArg = true,
|
|
3383
|
+
refreshSessionListArg = true,
|
|
3070
3384
|
): boolean {
|
|
3071
3385
|
const currentEpoch = this.liveMessageStreamEpochs.get(harnessIdArg) ?? 0;
|
|
3072
3386
|
if (epochArg < currentEpoch) return false;
|
|
@@ -3083,7 +3397,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3083
3397
|
this.invalidateDetailEnrichment();
|
|
3084
3398
|
void this.refreshSelectedSessionDetail();
|
|
3085
3399
|
}
|
|
3086
|
-
this.refreshSessionsNow();
|
|
3400
|
+
if (refreshSessionListArg) this.refreshSessionsNow();
|
|
3087
3401
|
}
|
|
3088
3402
|
}
|
|
3089
3403
|
return true;
|
|
@@ -3153,6 +3467,20 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3153
3467
|
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3154
3468
|
detailArg: IControllerSessionRenderDetail,
|
|
3155
3469
|
): boolean {
|
|
3470
|
+
const target = this.authoritativeReasoningTarget(updateArg, detailArg);
|
|
3471
|
+
return target !== undefined
|
|
3472
|
+
&& (target.part.text === updateArg.text || target.part.text.startsWith(updateArg.text))
|
|
3473
|
+
&& (
|
|
3474
|
+
updateArg.status === 'running'
|
|
3475
|
+
|| target.part.endedAt !== undefined
|
|
3476
|
+
|| target.message.streaming !== true
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
private authoritativeReasoningTarget(
|
|
3481
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3482
|
+
detailArg: IControllerSessionRenderDetail,
|
|
3483
|
+
): { message: interfaces.IControllerMessage; part: interfaces.IControllerReasoningPart } | undefined {
|
|
3156
3484
|
const message = detailArg.messages.find((messageArg) => (
|
|
3157
3485
|
messageArg.role === 'assistant'
|
|
3158
3486
|
&& (
|
|
@@ -3169,16 +3497,24 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3169
3497
|
const part = message?.reasoning?.find((partArg) => (
|
|
3170
3498
|
controllerRuntimeIdsEqual(partArg.id, updateArg.partId)
|
|
3171
3499
|
));
|
|
3172
|
-
return part
|
|
3173
|
-
&& (part.text === updateArg.text || part.text.startsWith(updateArg.text))
|
|
3174
|
-
&& (updateArg.status === 'running' || part.endedAt !== undefined || message?.streaming !== true);
|
|
3500
|
+
return message && part ? { message, part } : undefined;
|
|
3175
3501
|
}
|
|
3176
3502
|
|
|
3177
3503
|
private authoritativeTextUpdateCovers(
|
|
3178
3504
|
updateArg: interfaces.IControllerTextUpdate,
|
|
3179
3505
|
detailArg: IControllerSessionRenderDetail,
|
|
3180
3506
|
): boolean {
|
|
3181
|
-
const message =
|
|
3507
|
+
const message = this.authoritativeTextTarget(updateArg, detailArg);
|
|
3508
|
+
return message !== undefined
|
|
3509
|
+
&& (message.text === updateArg.text || message.text.startsWith(updateArg.text))
|
|
3510
|
+
&& (updateArg.status === 'running' || message.streaming !== true);
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
private authoritativeTextTarget(
|
|
3514
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
3515
|
+
detailArg: IControllerSessionRenderDetail,
|
|
3516
|
+
): interfaces.IControllerMessage | undefined {
|
|
3517
|
+
return detailArg.messages.find((messageArg) => (
|
|
3182
3518
|
messageArg.role === 'assistant'
|
|
3183
3519
|
&& (
|
|
3184
3520
|
controllerRuntimeIdsEqual(messageArg.id, updateArg.partId)
|
|
@@ -3189,9 +3525,6 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3189
3525
|
)
|
|
3190
3526
|
)
|
|
3191
3527
|
));
|
|
3192
|
-
return message !== undefined
|
|
3193
|
-
&& (message.text === updateArg.text || message.text.startsWith(updateArg.text))
|
|
3194
|
-
&& (updateArg.status === 'running' || message.streaming !== true);
|
|
3195
3528
|
}
|
|
3196
3529
|
|
|
3197
3530
|
private liveToolSnapshotIsNewer(
|
|
@@ -3259,6 +3592,257 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3259
3592
|
return this.authoritativeToolCallCovers(merged, toolCallArg) ? undefined : merged;
|
|
3260
3593
|
}
|
|
3261
3594
|
|
|
3595
|
+
private ensureCanonicalChatProjection(
|
|
3596
|
+
detailArg: IControllerSessionRenderDetail,
|
|
3597
|
+
projectIdArg: string,
|
|
3598
|
+
sessionIdArg: TBrowserSessionId,
|
|
3599
|
+
): ICanonicalChatProjection {
|
|
3600
|
+
const key = this.sessionOperationKey(projectIdArg, sessionIdArg);
|
|
3601
|
+
let projection = this.canonicalChatProjection;
|
|
3602
|
+
if (!projection || projection.key !== key) {
|
|
3603
|
+
projection = {
|
|
3604
|
+
key,
|
|
3605
|
+
sourceDetail: undefined,
|
|
3606
|
+
messages: [],
|
|
3607
|
+
messagesById: new Map(),
|
|
3608
|
+
messagesByOrder: new Map(),
|
|
3609
|
+
reasoningById: new Map(),
|
|
3610
|
+
};
|
|
3611
|
+
this.canonicalChatProjection = projection;
|
|
3612
|
+
this.canonicalChatProjectionDirty = true;
|
|
3613
|
+
}
|
|
3614
|
+
if (projection.sourceDetail === detailArg && !this.canonicalChatProjectionDirty) {
|
|
3615
|
+
return projection;
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
const messages = detailArg.messages
|
|
3619
|
+
.filter((messageArg) => messageArg.toolCall?.name !== 'question')
|
|
3620
|
+
.map((messageArg) => this.toHarnessMessage(messageArg, projectIdArg, sessionIdArg));
|
|
3621
|
+
const visibleMessageIds = new Set(messages.map((message) => message.id));
|
|
3622
|
+
for (const prompt of detailArg.pendingPrompts) {
|
|
3623
|
+
const id = controllerRuntimeIdToUiKey(prompt.id);
|
|
3624
|
+
if (visibleMessageIds.has(id)) continue;
|
|
3625
|
+
visibleMessageIds.add(id);
|
|
3626
|
+
messages.push({
|
|
3627
|
+
id,
|
|
3628
|
+
role: 'user',
|
|
3629
|
+
text: prompt.text,
|
|
3630
|
+
createdAt: prompt.createdAt,
|
|
3631
|
+
markdown: false,
|
|
3632
|
+
attachments: prompt.attachments.map((attachment) => ({ ...attachment })),
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
this.mergeLiveReasoningMessages(messages, projectIdArg, sessionIdArg);
|
|
3636
|
+
this.mergeLiveTextMessages(messages, projectIdArg, sessionIdArg);
|
|
3637
|
+
this.mergeLiveToolMessages(messages, projectIdArg, sessionIdArg);
|
|
3638
|
+
const structureChanged = this.canonicalHarnessStructureChanged(
|
|
3639
|
+
projection.messages,
|
|
3640
|
+
messages,
|
|
3641
|
+
);
|
|
3642
|
+
this.reconcileCanonicalHarnessMessages(projection, messages);
|
|
3643
|
+
this.canonicalTimelineRefreshPending ||= structureChanged;
|
|
3644
|
+
projection.sourceDetail = detailArg;
|
|
3645
|
+
this.canonicalChatProjectionDirty = false;
|
|
3646
|
+
return projection;
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
private canonicalHarnessStructureChanged(
|
|
3650
|
+
currentArg: readonly plugins.deesCatalog.IHarnessMessage[],
|
|
3651
|
+
nextArg: readonly plugins.deesCatalog.IHarnessMessage[],
|
|
3652
|
+
): boolean {
|
|
3653
|
+
return currentArg.length !== nextArg.length || currentArg.some((current, index) => {
|
|
3654
|
+
const next = nextArg[index];
|
|
3655
|
+
return !next
|
|
3656
|
+
|| current.id !== next.id
|
|
3657
|
+
|| current.role !== next.role
|
|
3658
|
+
|| current.toolCall?.id !== next.toolCall?.id
|
|
3659
|
+
|| current.toolCall?.name !== next.toolCall?.name
|
|
3660
|
+
|| !this.liveMessageOrdersEqual(current.order, next.order);
|
|
3661
|
+
});
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
private reconcileCanonicalHarnessMessages(
|
|
3665
|
+
projectionArg: ICanonicalChatProjection,
|
|
3666
|
+
nextMessagesArg: plugins.deesCatalog.IHarnessMessage[],
|
|
3667
|
+
): void {
|
|
3668
|
+
const currentById = new Map(
|
|
3669
|
+
projectionArg.messages.map((message) => [message.id, message]),
|
|
3670
|
+
);
|
|
3671
|
+
const reconciled = nextMessagesArg.map((nextMessage) => {
|
|
3672
|
+
const current = currentById.get(nextMessage.id);
|
|
3673
|
+
if (!current) return nextMessage;
|
|
3674
|
+
if (!controllerJsonValuesEqual(current, nextMessage)) {
|
|
3675
|
+
this.canonicalMessageRefreshIds.add(current.id);
|
|
3676
|
+
}
|
|
3677
|
+
this.reconcileCanonicalHarnessMessage(current, nextMessage);
|
|
3678
|
+
return current;
|
|
3679
|
+
});
|
|
3680
|
+
projectionArg.messages.splice(0, projectionArg.messages.length, ...reconciled);
|
|
3681
|
+
this.rebuildCanonicalChatIndexes(projectionArg);
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
private reconcileCanonicalHarnessMessage(
|
|
3685
|
+
targetArg: plugins.deesCatalog.IHarnessMessage,
|
|
3686
|
+
sourceArg: plugins.deesCatalog.IHarnessMessage,
|
|
3687
|
+
): void {
|
|
3688
|
+
const targetReasoning = targetArg.reasoning;
|
|
3689
|
+
const sourceReasoning = sourceArg.reasoning;
|
|
3690
|
+
const targetToolCall = targetArg.toolCall;
|
|
3691
|
+
const sourceToolCall = sourceArg.toolCall;
|
|
3692
|
+
const target = targetArg as unknown as Record<string, unknown>;
|
|
3693
|
+
const source = sourceArg as unknown as Record<string, unknown>;
|
|
3694
|
+
for (const key of Object.keys(target)) {
|
|
3695
|
+
if (!(key in source)) delete target[key];
|
|
3696
|
+
}
|
|
3697
|
+
for (const [key, value] of Object.entries(source)) {
|
|
3698
|
+
if (key !== 'reasoning' && key !== 'toolCall') target[key] = value;
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
if (sourceReasoning) {
|
|
3702
|
+
const currentById = new Map((targetReasoning ?? []).map((part) => [part.id, part]));
|
|
3703
|
+
const reasoning = sourceReasoning.map((sourcePart) => {
|
|
3704
|
+
const currentPart = currentById.get(sourcePart.id);
|
|
3705
|
+
if (!currentPart) return sourcePart;
|
|
3706
|
+
const current = currentPart as unknown as Record<string, unknown>;
|
|
3707
|
+
const next = sourcePart as unknown as Record<string, unknown>;
|
|
3708
|
+
for (const key of Object.keys(current)) {
|
|
3709
|
+
if (!(key in next)) delete current[key];
|
|
3710
|
+
}
|
|
3711
|
+
Object.assign(current, next);
|
|
3712
|
+
return currentPart;
|
|
3713
|
+
});
|
|
3714
|
+
if (targetReasoning) targetReasoning.splice(0, targetReasoning.length, ...reasoning);
|
|
3715
|
+
else targetArg.reasoning = reasoning;
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
if (sourceToolCall) {
|
|
3719
|
+
if (targetToolCall?.id === sourceToolCall.id) {
|
|
3720
|
+
const current = targetToolCall as unknown as Record<string, unknown>;
|
|
3721
|
+
const next = sourceToolCall as unknown as Record<string, unknown>;
|
|
3722
|
+
for (const key of Object.keys(current)) {
|
|
3723
|
+
if (!(key in next)) delete current[key];
|
|
3724
|
+
}
|
|
3725
|
+
Object.assign(current, next);
|
|
3726
|
+
targetArg.toolCall = targetToolCall;
|
|
3727
|
+
} else {
|
|
3728
|
+
targetArg.toolCall = sourceToolCall;
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
private rebuildCanonicalChatIndexes(projectionArg: ICanonicalChatProjection): void {
|
|
3734
|
+
projectionArg.messagesById.clear();
|
|
3735
|
+
projectionArg.messagesByOrder.clear();
|
|
3736
|
+
projectionArg.reasoningById.clear();
|
|
3737
|
+
for (const message of projectionArg.messages) {
|
|
3738
|
+
projectionArg.messagesById.set(message.id, message);
|
|
3739
|
+
if (message.order && !projectionArg.messagesByOrder.has(transcriptOrderKey(message.order))) {
|
|
3740
|
+
projectionArg.messagesByOrder.set(transcriptOrderKey(message.order), message);
|
|
3741
|
+
}
|
|
3742
|
+
for (const part of message.reasoning ?? []) {
|
|
3743
|
+
projectionArg.reasoningById.set(part.id, { message, part });
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
private selectedCanonicalChatProjection(
|
|
3749
|
+
projectIdArg: string,
|
|
3750
|
+
sessionIdArg: TBrowserSessionId,
|
|
3751
|
+
): ICanonicalChatProjection | undefined {
|
|
3752
|
+
const detail = this.sessionDetail;
|
|
3753
|
+
if (
|
|
3754
|
+
projectIdArg !== this.selectedProjectId
|
|
3755
|
+
|| !detail
|
|
3756
|
+
|| !controllerRuntimeIdsEqual(detail.session.id, sessionIdArg)
|
|
3757
|
+
|| !controllerRuntimeIdsEqual(this.selectedSessionId, sessionIdArg)
|
|
3758
|
+
) return undefined;
|
|
3759
|
+
return this.ensureCanonicalChatProjection(detail, projectIdArg, sessionIdArg);
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
private canonicalTextTarget(
|
|
3763
|
+
projectionArg: ICanonicalChatProjection,
|
|
3764
|
+
updateArg: interfaces.IControllerTextUpdate | interfaces.IControllerTextDelta,
|
|
3765
|
+
): plugins.deesCatalog.IHarnessMessage | undefined {
|
|
3766
|
+
return projectionArg.messagesById.get(controllerRuntimeIdToUiKey(updateArg.messageId))
|
|
3767
|
+
?? projectionArg.messagesById.get(controllerRuntimeIdToUiKey(updateArg.partId))
|
|
3768
|
+
?? (updateArg.order
|
|
3769
|
+
? projectionArg.messagesByOrder.get(transcriptOrderKey(updateArg.order))
|
|
3770
|
+
: undefined);
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
private canonicalReasoningTarget(
|
|
3774
|
+
projectionArg: ICanonicalChatProjection,
|
|
3775
|
+
updateArg: interfaces.IControllerReasoningUpdate | interfaces.IControllerReasoningDelta,
|
|
3776
|
+
): ICanonicalReasoningTarget | undefined {
|
|
3777
|
+
const reasoningId = controllerRuntimeIdToUiKey(updateArg.partId);
|
|
3778
|
+
const partTarget = projectionArg.reasoningById.get(reasoningId);
|
|
3779
|
+
const message = projectionArg.messagesById.get(controllerRuntimeIdToUiKey(updateArg.messageId))
|
|
3780
|
+
?? partTarget?.message
|
|
3781
|
+
?? (updateArg.order
|
|
3782
|
+
? projectionArg.messagesByOrder.get(transcriptOrderKey(updateArg.order))
|
|
3783
|
+
: undefined);
|
|
3784
|
+
if (!message) return undefined;
|
|
3785
|
+
const part = partTarget?.message === message
|
|
3786
|
+
? partTarget.part
|
|
3787
|
+
: message.reasoning?.find((candidate) => candidate.id === reasoningId);
|
|
3788
|
+
return part ? { message, part } : undefined;
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
private mountedCanonicalChat(
|
|
3792
|
+
projectionArg: ICanonicalChatProjection,
|
|
3793
|
+
): InstanceType<typeof plugins.deesCatalog.DeesHarnessChat> | undefined {
|
|
3794
|
+
const chat = this.shadowRoot?.querySelector('dees-harness-chat');
|
|
3795
|
+
return chat instanceof plugins.deesCatalog.DeesHarnessChat
|
|
3796
|
+
&& chat.messages === projectionArg.messages
|
|
3797
|
+
&& chat.transcriptKey === projectionArg.key
|
|
3798
|
+
? chat
|
|
3799
|
+
: undefined;
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
private applyCanonicalTextDelta(
|
|
3803
|
+
projectionArg: ICanonicalChatProjection,
|
|
3804
|
+
messageArg: plugins.deesCatalog.IHarnessMessage,
|
|
3805
|
+
deltaArg: string,
|
|
3806
|
+
): boolean {
|
|
3807
|
+
const previousText = messageArg.text;
|
|
3808
|
+
const expectedText = `${previousText}${deltaArg}`;
|
|
3809
|
+
const chat = this.mountedCanonicalChat(projectionArg);
|
|
3810
|
+
chat?.applyDelta({ type: 'text', messageId: messageArg.id, delta: deltaArg });
|
|
3811
|
+
if (messageArg.text === previousText) {
|
|
3812
|
+
messageArg.text = expectedText;
|
|
3813
|
+
messageArg.streaming = true;
|
|
3814
|
+
}
|
|
3815
|
+
if (messageArg.text === expectedText) return true;
|
|
3816
|
+
messageArg.text = previousText;
|
|
3817
|
+
return false;
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
private applyCanonicalReasoningDelta(
|
|
3821
|
+
projectionArg: ICanonicalChatProjection,
|
|
3822
|
+
targetArg: ICanonicalReasoningTarget,
|
|
3823
|
+
deltaArg: string,
|
|
3824
|
+
): boolean {
|
|
3825
|
+
const previousText = targetArg.part.text;
|
|
3826
|
+
const expectedText = `${previousText}${deltaArg}`;
|
|
3827
|
+
const chat = this.mountedCanonicalChat(projectionArg);
|
|
3828
|
+
chat?.applyDelta({
|
|
3829
|
+
type: 'reasoning',
|
|
3830
|
+
messageId: targetArg.message.id,
|
|
3831
|
+
partId: targetArg.part.id,
|
|
3832
|
+
delta: deltaArg,
|
|
3833
|
+
});
|
|
3834
|
+
if (targetArg.part.text === previousText) targetArg.part.text = expectedText;
|
|
3835
|
+
if (targetArg.part.text === expectedText) return true;
|
|
3836
|
+
targetArg.part.text = previousText;
|
|
3837
|
+
return false;
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
private requestCanonicalChatReconciliation(structureMayChangeArg = true): void {
|
|
3841
|
+
this.canonicalChatProjectionDirty = true;
|
|
3842
|
+
this.canonicalTimelineRefreshPending ||= structureMayChangeArg;
|
|
3843
|
+
this.requestUpdate();
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3262
3846
|
private applyLiveToolExecution(
|
|
3263
3847
|
projectIdArg: string,
|
|
3264
3848
|
executionArg: interfaces.IControllerToolExecution,
|
|
@@ -3332,24 +3916,22 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3332
3916
|
}
|
|
3333
3917
|
this.reconcileSubtaskPreviews(projectIdArg, executionArg.sessionId);
|
|
3334
3918
|
|
|
3335
|
-
const
|
|
3919
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, executionArg.sessionId);
|
|
3920
|
+
if (projection && this.canonicalTimelineRefreshPending) this.requestUpdate();
|
|
3336
3921
|
const messageId = controllerRuntimeIdToUiKey(execution.partId);
|
|
3337
3922
|
const harnessToolCall = this.toHarnessLiveToolCall(execution, projectIdArg);
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
&& chat.messages.some((messageArg) => messageArg.id === messageId)
|
|
3341
|
-
) {
|
|
3342
|
-
const message = chat.messages.find((messageArg) => messageArg.id === messageId)!;
|
|
3923
|
+
const message = projection?.messagesById.get(messageId);
|
|
3924
|
+
if (projection && message) {
|
|
3343
3925
|
message.toolCall = harnessToolCall;
|
|
3344
3926
|
message.streaming = execution.status === 'pending' || execution.status === 'running';
|
|
3345
3927
|
message.updatedAt = execution.finishedAt ?? execution.sourceUpdatedAt;
|
|
3346
|
-
|
|
3928
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
3347
3929
|
type: 'tool-update',
|
|
3348
3930
|
messageId,
|
|
3349
3931
|
tool: harnessToolCall,
|
|
3350
3932
|
});
|
|
3351
3933
|
} else {
|
|
3352
|
-
this.
|
|
3934
|
+
this.requestCanonicalChatReconciliation();
|
|
3353
3935
|
}
|
|
3354
3936
|
}
|
|
3355
3937
|
|
|
@@ -3388,23 +3970,59 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3388
3970
|
if (cursorArg.streamEpoch < (this.liveMessageStreamEpochs.get(harnessId) ?? 0)) return false;
|
|
3389
3971
|
if (!this.acceptLiveMessageEpoch(harnessId, cursorArg.streamEpoch, false)) return false;
|
|
3390
3972
|
const sessionKey = this.liveToolSessionKey(projectIdArg, sessionIdArg);
|
|
3973
|
+
const deltaBlockKey = this.sessionOperationKey(projectIdArg, sessionIdArg);
|
|
3974
|
+
const recoveringDeltaGap = this.liveMessageDeltaBlocks.get(deltaBlockKey)?.streamEpoch
|
|
3975
|
+
=== cursorArg.streamEpoch;
|
|
3391
3976
|
this.liveMessageHydrationFloors.set(sessionKey, { sessionId: sessionIdArg, cursor: cursorArg });
|
|
3392
3977
|
for (const [key, update] of this.liveReasoningUpdates) {
|
|
3978
|
+
const target = this.authoritativeReasoningTarget(update, detailArg);
|
|
3393
3979
|
if (
|
|
3394
3980
|
key.startsWith(`${projectIdArg}\0`)
|
|
3395
3981
|
&& controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
3396
3982
|
&& this.hydrationCursorCovers(cursorArg, update)
|
|
3397
|
-
&&
|
|
3398
|
-
|
|
3983
|
+
&& target !== undefined
|
|
3984
|
+
&& (recoveringDeltaGap || this.authoritativeReasoningUpdateCovers(update, detailArg))
|
|
3985
|
+
) this.retireLiveReasoningUpdateToBaseline(key, update, target.part.text, cursorArg);
|
|
3399
3986
|
}
|
|
3400
3987
|
for (const [key, update] of this.liveTextUpdates) {
|
|
3988
|
+
const target = this.authoritativeTextTarget(update, detailArg);
|
|
3401
3989
|
if (
|
|
3402
3990
|
key.startsWith(`${projectIdArg}\0`)
|
|
3403
3991
|
&& controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
3404
3992
|
&& this.hydrationCursorCovers(cursorArg, update)
|
|
3405
|
-
&&
|
|
3406
|
-
|
|
3993
|
+
&& target !== undefined
|
|
3994
|
+
&& (recoveringDeltaGap || this.authoritativeTextUpdateCovers(update, detailArg))
|
|
3995
|
+
) this.retireLiveTextUpdateToBaseline(key, update, target.text, cursorArg);
|
|
3996
|
+
}
|
|
3997
|
+
for (const [key, baseline] of [...this.liveReasoningBaselines]) {
|
|
3998
|
+
const update = baseline.update;
|
|
3999
|
+
if (
|
|
4000
|
+
!key.startsWith(`${projectIdArg}\0`)
|
|
4001
|
+
|| !controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
4002
|
+
|| !this.hydrationCursorCovers(cursorArg, update)
|
|
4003
|
+
) continue;
|
|
4004
|
+
const target = this.authoritativeReasoningTarget(update, detailArg);
|
|
4005
|
+
if (target) {
|
|
4006
|
+
this.retireLiveReasoningUpdateToBaseline(key, update, target.part.text, cursorArg);
|
|
4007
|
+
} else if (recoveringDeltaGap) {
|
|
4008
|
+
this.deleteLiveReasoningBaseline(key);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
for (const [key, baseline] of [...this.liveTextBaselines]) {
|
|
4012
|
+
const update = baseline.update;
|
|
4013
|
+
if (
|
|
4014
|
+
!key.startsWith(`${projectIdArg}\0`)
|
|
4015
|
+
|| !controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
4016
|
+
|| !this.hydrationCursorCovers(cursorArg, update)
|
|
4017
|
+
) continue;
|
|
4018
|
+
const target = this.authoritativeTextTarget(update, detailArg);
|
|
4019
|
+
if (target) {
|
|
4020
|
+
this.retireLiveTextUpdateToBaseline(key, update, target.text, cursorArg);
|
|
4021
|
+
} else if (recoveringDeltaGap) {
|
|
4022
|
+
this.deleteLiveTextBaseline(key);
|
|
4023
|
+
}
|
|
3407
4024
|
}
|
|
4025
|
+
this.liveMessageDeltaBlocks.delete(deltaBlockKey);
|
|
3408
4026
|
return true;
|
|
3409
4027
|
}
|
|
3410
4028
|
|
|
@@ -3448,6 +4066,30 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3448
4066
|
|| !controllerRuntimeIdsEqual(update.sessionId, sessionId)
|
|
3449
4067
|
) this.deleteLiveTextUpdate(key);
|
|
3450
4068
|
}
|
|
4069
|
+
for (const [key, baseline] of this.liveReasoningBaselines) {
|
|
4070
|
+
if (
|
|
4071
|
+
!projectId
|
|
4072
|
+
|| !isSessionRuntimeId(sessionId)
|
|
4073
|
+
|| !key.startsWith(`${projectId}\0`)
|
|
4074
|
+
|| !controllerRuntimeIdsEqual(baseline.update.sessionId, sessionId)
|
|
4075
|
+
) this.deleteLiveReasoningBaseline(key);
|
|
4076
|
+
}
|
|
4077
|
+
for (const [key, baseline] of this.liveTextBaselines) {
|
|
4078
|
+
if (
|
|
4079
|
+
!projectId
|
|
4080
|
+
|| !isSessionRuntimeId(sessionId)
|
|
4081
|
+
|| !key.startsWith(`${projectId}\0`)
|
|
4082
|
+
|| !controllerRuntimeIdsEqual(baseline.update.sessionId, sessionId)
|
|
4083
|
+
) this.deleteLiveTextBaseline(key);
|
|
4084
|
+
}
|
|
4085
|
+
for (const [key, block] of this.liveMessageDeltaBlocks) {
|
|
4086
|
+
if (
|
|
4087
|
+
!projectId
|
|
4088
|
+
|| !isSessionRuntimeId(sessionId)
|
|
4089
|
+
|| key !== this.sessionOperationKey(projectId, sessionId)
|
|
4090
|
+
|| !controllerRuntimeIdsEqual(block.sessionId, sessionId)
|
|
4091
|
+
) this.liveMessageDeltaBlocks.delete(key);
|
|
4092
|
+
}
|
|
3451
4093
|
}
|
|
3452
4094
|
|
|
3453
4095
|
private subtaskPreviewKey(
|
|
@@ -3667,7 +4309,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3667
4309
|
if (isBusy) this.queueSubtaskPreviewHydration(entry);
|
|
3668
4310
|
changed = true;
|
|
3669
4311
|
}
|
|
3670
|
-
if (changed) this.
|
|
4312
|
+
if (changed) this.requestCanonicalChatReconciliation();
|
|
3671
4313
|
}
|
|
3672
4314
|
|
|
3673
4315
|
private queueSubtaskPreviewHydration(entryArg: ISubtaskPreviewEntry): void {
|
|
@@ -3795,7 +4437,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3795
4437
|
messages,
|
|
3796
4438
|
...((page.truncated === true || page.nextCursor !== undefined) ? { truncated: true } : {}),
|
|
3797
4439
|
};
|
|
3798
|
-
this.
|
|
4440
|
+
this.requestCanonicalChatReconciliation();
|
|
3799
4441
|
} catch {
|
|
3800
4442
|
if (abortControllerArg.signal.aborted) return;
|
|
3801
4443
|
if (!this.subtaskPreviewEntryIsCurrent(entryArg)) return;
|
|
@@ -3807,7 +4449,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3807
4449
|
status: entryArg.stream.status,
|
|
3808
4450
|
notice: subtaskPreviewUnavailableNotice,
|
|
3809
4451
|
};
|
|
3810
|
-
this.
|
|
4452
|
+
this.requestCanonicalChatReconciliation();
|
|
3811
4453
|
}
|
|
3812
4454
|
}
|
|
3813
4455
|
|
|
@@ -3840,7 +4482,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3840
4482
|
this.queueSubtaskPreviewHydration(entry);
|
|
3841
4483
|
}, subtaskPreviewRefreshDelayMs);
|
|
3842
4484
|
}
|
|
3843
|
-
if (changed) this.
|
|
4485
|
+
if (changed) this.requestCanonicalChatReconciliation();
|
|
3844
4486
|
}
|
|
3845
4487
|
|
|
3846
4488
|
private clearSubtaskPreviewState(): void {
|
|
@@ -3851,6 +4493,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3851
4493
|
}
|
|
3852
4494
|
this.subtaskPreviewEntries.clear();
|
|
3853
4495
|
this.subtaskPreviewHydrationQueue = [];
|
|
4496
|
+
this.canonicalChatProjectionDirty = true;
|
|
3854
4497
|
}
|
|
3855
4498
|
|
|
3856
4499
|
private applySessionEventState(eventArg: interfaces.IControllerEvent): void {
|
|
@@ -4001,7 +4644,9 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4001
4644
|
&& this.socketClient.isConnected
|
|
4002
4645
|
) {
|
|
4003
4646
|
this.refreshPending = false;
|
|
4004
|
-
|
|
4647
|
+
const includeRelated = this.refreshRelatedPending;
|
|
4648
|
+
this.refreshRelatedPending = false;
|
|
4649
|
+
await this.performSessionRefresh(includeRelated);
|
|
4005
4650
|
}
|
|
4006
4651
|
} finally {
|
|
4007
4652
|
this.refreshInFlight = undefined;
|
|
@@ -4041,7 +4686,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4041
4686
|
}
|
|
4042
4687
|
}
|
|
4043
4688
|
|
|
4044
|
-
private async performSessionRefresh(): Promise<void> {
|
|
4689
|
+
private async performSessionRefresh(includeRelatedArg = true): Promise<void> {
|
|
4045
4690
|
const projectId = this.selectedProjectId;
|
|
4046
4691
|
if (!isNonEmptyString(projectId)) {
|
|
4047
4692
|
this.sessions = [];
|
|
@@ -4062,10 +4707,14 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4062
4707
|
&& controllerRuntimeIdsEqual(sessionArg.id, selectedSessionAtStart)
|
|
4063
4708
|
&& sessionArg.archivedAt !== undefined
|
|
4064
4709
|
));
|
|
4065
|
-
if (selectedSessionAtStart)
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4710
|
+
if (includeRelatedArg && selectedSessionAtStart) {
|
|
4711
|
+
void this.refreshSelectedSessionDetail(false);
|
|
4712
|
+
}
|
|
4713
|
+
if (includeRelatedArg) {
|
|
4714
|
+
void this.loadSessionGroups();
|
|
4715
|
+
void this.loadTerminals();
|
|
4716
|
+
void this.loadResources();
|
|
4717
|
+
}
|
|
4069
4718
|
// The loading state is for a list we cannot show yet; background refreshes
|
|
4070
4719
|
// must not flip it, or the sidebar flashes on every pushed event.
|
|
4071
4720
|
const initialLoad = this.sessionsLoadedForProjectId !== projectId;
|
|
@@ -4091,11 +4740,12 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4091
4740
|
? response.sessions.filter(hasSessionRuntimeId)
|
|
4092
4741
|
: [];
|
|
4093
4742
|
this.applySessionSnapshot(projectId, nextSessions, requestEventRevision);
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4743
|
+
const reconciledSessions = reconcileKeyedJsonArray(
|
|
4744
|
+
this.sessions,
|
|
4745
|
+
nextSessions,
|
|
4746
|
+
(session) => controllerRuntimeIdToUiKey(session.id),
|
|
4747
|
+
);
|
|
4748
|
+
if (reconciledSessions !== this.sessions) this.sessions = reconciledSessions;
|
|
4099
4749
|
const selectedSessionBecameArchived = selectedSessionAtStart !== undefined
|
|
4100
4750
|
&& !selectedSessionWasArchived
|
|
4101
4751
|
&& this.sessions.some((sessionArg) => (
|
|
@@ -4171,6 +4821,107 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4171
4821
|
}
|
|
4172
4822
|
}
|
|
4173
4823
|
|
|
4824
|
+
private reconcileControllerMessages(
|
|
4825
|
+
currentArg: readonly interfaces.IControllerMessage[],
|
|
4826
|
+
nextArg: readonly interfaces.IControllerMessage[],
|
|
4827
|
+
): interfaces.IControllerMessage[] {
|
|
4828
|
+
return reconcileKeyedJsonArray(
|
|
4829
|
+
currentArg,
|
|
4830
|
+
nextArg,
|
|
4831
|
+
(message) => controllerRuntimeIdToUiKey(message.id),
|
|
4832
|
+
);
|
|
4833
|
+
}
|
|
4834
|
+
|
|
4835
|
+
private reconcileControllerMessageBundles(
|
|
4836
|
+
currentArg: readonly interfaces.IControllerMessageBundle[],
|
|
4837
|
+
nextArg: readonly interfaces.IControllerMessageBundle[],
|
|
4838
|
+
): interfaces.IControllerMessageBundle[] {
|
|
4839
|
+
const currentByKey = new Map(currentArg.map((bundle) => [
|
|
4840
|
+
controllerRuntimeIdToUiKey(bundle.sourceMessageId),
|
|
4841
|
+
bundle,
|
|
4842
|
+
]));
|
|
4843
|
+
let unchanged = currentArg.length === nextArg.length;
|
|
4844
|
+
const bundles = nextArg.map((nextBundle, index) => {
|
|
4845
|
+
const current = currentByKey.get(controllerRuntimeIdToUiKey(nextBundle.sourceMessageId));
|
|
4846
|
+
if (!current) {
|
|
4847
|
+
unchanged = false;
|
|
4848
|
+
return nextBundle;
|
|
4849
|
+
}
|
|
4850
|
+
const messages = this.reconcileControllerMessages(current.messages, nextBundle.messages);
|
|
4851
|
+
const bundle = current.structuralDigest === nextBundle.structuralDigest
|
|
4852
|
+
&& messages === current.messages
|
|
4853
|
+
&& controllerRuntimeIdsEqual(current.sourceMessageId, nextBundle.sourceMessageId)
|
|
4854
|
+
? current
|
|
4855
|
+
: { ...nextBundle, messages };
|
|
4856
|
+
unchanged &&= bundle === currentArg[index];
|
|
4857
|
+
return bundle;
|
|
4858
|
+
});
|
|
4859
|
+
return unchanged ? currentArg as interfaces.IControllerMessageBundle[] : bundles;
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
private reconcileSessionRenderDetail(
|
|
4863
|
+
currentArg: IControllerSessionRenderDetail | undefined,
|
|
4864
|
+
nextArg: IControllerSessionRenderDetail,
|
|
4865
|
+
): IControllerSessionRenderDetail {
|
|
4866
|
+
if (!currentArg || !controllerRuntimeIdsEqual(currentArg.session.id, nextArg.session.id)) {
|
|
4867
|
+
return nextArg;
|
|
4868
|
+
}
|
|
4869
|
+
const reuse = <TValue>(currentValueArg: TValue, nextValueArg: TValue): TValue => (
|
|
4870
|
+
controllerJsonValuesEqual(currentValueArg, nextValueArg) ? currentValueArg : nextValueArg
|
|
4871
|
+
);
|
|
4872
|
+
const messages = this.reconcileControllerMessages(currentArg.messages, nextArg.messages);
|
|
4873
|
+
const pendingPrompts = reconcileKeyedJsonArray(
|
|
4874
|
+
currentArg.pendingPrompts,
|
|
4875
|
+
nextArg.pendingPrompts,
|
|
4876
|
+
(prompt) => controllerRuntimeIdToUiKey(prompt.id),
|
|
4877
|
+
);
|
|
4878
|
+
const permissions = reconcileKeyedJsonArray(
|
|
4879
|
+
currentArg.permissions,
|
|
4880
|
+
nextArg.permissions,
|
|
4881
|
+
(permission) => controllerRuntimeIdToUiKey(permission.id),
|
|
4882
|
+
);
|
|
4883
|
+
const questions = reconcileKeyedJsonArray(
|
|
4884
|
+
currentArg.questions,
|
|
4885
|
+
nextArg.questions,
|
|
4886
|
+
(question) => controllerRuntimeIdToUiKey(question.id),
|
|
4887
|
+
);
|
|
4888
|
+
const todos = reconcileKeyedJsonArray(
|
|
4889
|
+
currentArg.todos,
|
|
4890
|
+
nextArg.todos,
|
|
4891
|
+
(todo, index) => todo.id ? controllerRuntimeIdToUiKey(todo.id) : `index:${index}`,
|
|
4892
|
+
);
|
|
4893
|
+
const intelligenceExchanges = reconcileKeyedJsonArray(
|
|
4894
|
+
currentArg.intelligenceExchanges,
|
|
4895
|
+
nextArg.intelligenceExchanges,
|
|
4896
|
+
(exchange) => exchange.id,
|
|
4897
|
+
);
|
|
4898
|
+
const detail: IControllerSessionRenderDetail = {
|
|
4899
|
+
...nextArg,
|
|
4900
|
+
session: reuse(currentArg.session, nextArg.session),
|
|
4901
|
+
messages,
|
|
4902
|
+
pendingPrompts,
|
|
4903
|
+
permissions,
|
|
4904
|
+
questions,
|
|
4905
|
+
todos,
|
|
4906
|
+
scratchpad: reuse(currentArg.scratchpad, nextArg.scratchpad),
|
|
4907
|
+
intelligenceExchanges,
|
|
4908
|
+
sessionMetrics: reuse(currentArg.sessionMetrics, nextArg.sessionMetrics),
|
|
4909
|
+
toolStreamCursor: reuse(currentArg.toolStreamCursor, nextArg.toolStreamCursor),
|
|
4910
|
+
messageStreamCursor: reuse(currentArg.messageStreamCursor, nextArg.messageStreamCursor),
|
|
4911
|
+
...(nextArg.modelChoice === undefined
|
|
4912
|
+
? {}
|
|
4913
|
+
: { modelChoice: reuse(currentArg.modelChoice, nextArg.modelChoice) }),
|
|
4914
|
+
};
|
|
4915
|
+
const current = currentArg as unknown as Record<string, unknown>;
|
|
4916
|
+
const reconciled = detail as unknown as Record<string, unknown>;
|
|
4917
|
+
const currentKeys = Object.keys(current);
|
|
4918
|
+
const nextKeys = Object.keys(reconciled);
|
|
4919
|
+
return currentKeys.length === nextKeys.length
|
|
4920
|
+
&& nextKeys.every((key) => Object.is(current[key], reconciled[key]))
|
|
4921
|
+
? currentArg
|
|
4922
|
+
: detail;
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4174
4925
|
private async loadSessionDetail(
|
|
4175
4926
|
sessionIdArg: TBrowserSessionId,
|
|
4176
4927
|
restartEnrichmentArg = true,
|
|
@@ -4240,9 +4991,14 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4240
4991
|
const historyBecameTerminal = sameSession
|
|
4241
4992
|
&& this.detailHistoryCursor !== undefined
|
|
4242
4993
|
&& response.messagePage.nextCursor === undefined;
|
|
4994
|
+
const sessionBecameSettled = sameSession
|
|
4995
|
+
&& (currentDetail!.session.status === 'busy' || currentDetail!.session.status === 'retry')
|
|
4996
|
+
&& response.session.status !== 'busy'
|
|
4997
|
+
&& response.session.status !== 'retry';
|
|
4243
4998
|
const restartEnrichment = restartEnrichmentArg
|
|
4244
4999
|
|| coreBoundaryChanged
|
|
4245
5000
|
|| historyBecameTerminal
|
|
5001
|
+
|| sessionBecameSettled
|
|
4246
5002
|
|| toolEpochAdvanced
|
|
4247
5003
|
|| messageEpochAdvanced;
|
|
4248
5004
|
const nextCoreBundleKeySet = new Set(nextCoreBundleKeys);
|
|
@@ -4279,6 +5035,10 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4279
5035
|
));
|
|
4280
5036
|
provisionalBundleKeys.clear();
|
|
4281
5037
|
}
|
|
5038
|
+
accumulated.bundles = this.reconcileControllerMessageBundles(
|
|
5039
|
+
this.detailBundles,
|
|
5040
|
+
accumulated.bundles,
|
|
5041
|
+
);
|
|
4282
5042
|
const { messagePage, ...coreDetail } = response;
|
|
4283
5043
|
const renderDetail: IControllerSessionRenderDetail = {
|
|
4284
5044
|
...coreDetail,
|
|
@@ -4351,9 +5111,9 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4351
5111
|
unavailable: this.detailHistoryLimits.unavailable,
|
|
4352
5112
|
};
|
|
4353
5113
|
}
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
5114
|
+
const reconciledDetail = this.reconcileSessionRenderDetail(this.sessionDetail, renderDetail);
|
|
5115
|
+
if (reconciledDetail !== this.sessionDetail) this.sessionDetail = reconciledDetail;
|
|
5116
|
+
this.ensureCanonicalChatProjection(reconciledDetail, projectId, sessionIdArg);
|
|
4357
5117
|
this.reconcileSubtaskPreviews(projectId, sessionIdArg);
|
|
4358
5118
|
// A draft that materialized keeps its placeholder view until this first
|
|
4359
5119
|
// real detail arrives; now the actual chat takes over.
|
|
@@ -4417,6 +5177,10 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4417
5177
|
this.detailHistoryOwnership = undefined;
|
|
4418
5178
|
this.detailHistoryLimits = emptyDetailHistoryLimits();
|
|
4419
5179
|
this.detailHistoryStatus = 'idle';
|
|
5180
|
+
this.canonicalChatProjection = undefined;
|
|
5181
|
+
this.canonicalChatProjectionDirty = false;
|
|
5182
|
+
this.canonicalTimelineRefreshPending = false;
|
|
5183
|
+
this.canonicalMessageRefreshIds.clear();
|
|
4420
5184
|
}
|
|
4421
5185
|
|
|
4422
5186
|
private invalidateDetailEnrichment(): void {
|
|
@@ -4546,14 +5310,19 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4546
5310
|
(bundle) => controllerRuntimeIdToUiKey(bundle.sourceMessageId),
|
|
4547
5311
|
);
|
|
4548
5312
|
if (provisionalContentChanged || !sameStringArray(previousKeys, nextKeys)) {
|
|
4549
|
-
this.detailBundles =
|
|
5313
|
+
this.detailBundles = this.reconcileControllerMessageBundles(
|
|
5314
|
+
this.detailBundles,
|
|
5315
|
+
bounded.bundles,
|
|
5316
|
+
);
|
|
4550
5317
|
this.detailBundleKeys = new Set(nextKeys);
|
|
4551
5318
|
const detail = this.sessionDetail;
|
|
4552
5319
|
if (!detail || !controllerRuntimeIdsEqual(detail.session.id, ownership.sessionId)) return;
|
|
4553
|
-
this.
|
|
5320
|
+
const nextDetail = this.reconcileSessionRenderDetail(detail, {
|
|
4554
5321
|
...detail,
|
|
4555
|
-
messages:
|
|
4556
|
-
};
|
|
5322
|
+
messages: this.detailBundles.flatMap((bundle) => bundle.messages),
|
|
5323
|
+
});
|
|
5324
|
+
if (nextDetail !== detail) this.sessionDetail = nextDetail;
|
|
5325
|
+
this.ensureCanonicalChatProjection(nextDetail, ownership.projectId, ownership.sessionId);
|
|
4557
5326
|
}
|
|
4558
5327
|
this.detailHistoryCursor = nextCursor;
|
|
4559
5328
|
if (nextCursor !== undefined) this.detailHistorySeenCursors.add(nextCursor);
|
|
@@ -6702,9 +7471,12 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
6702
7471
|
const terminals = Array.isArray(response.terminals)
|
|
6703
7472
|
? response.terminals.filter(hasTerminalRuntimeId)
|
|
6704
7473
|
: [];
|
|
6705
|
-
|
|
6706
|
-
this.terminals
|
|
6707
|
-
|
|
7474
|
+
const reconciledTerminals = reconcileKeyedJsonArray(
|
|
7475
|
+
this.terminals,
|
|
7476
|
+
terminals,
|
|
7477
|
+
(terminal) => controllerRuntimeIdToUiKey(terminal.id),
|
|
7478
|
+
);
|
|
7479
|
+
if (reconciledTerminals !== this.terminals) this.terminals = reconciledTerminals;
|
|
6708
7480
|
this.terminalsLoadedForProjectId = projectId;
|
|
6709
7481
|
if (
|
|
6710
7482
|
this.selectedTerminalId !== undefined
|
|
@@ -6976,6 +7748,17 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
6976
7748
|
public updated(changedPropertiesArg: Map<PropertyKey, unknown>): void {
|
|
6977
7749
|
super.updated(changedPropertiesArg);
|
|
6978
7750
|
this.flushComposerFocus();
|
|
7751
|
+
const projection = this.canonicalChatProjection;
|
|
7752
|
+
const chat = projection ? this.mountedCanonicalChat(projection) : undefined;
|
|
7753
|
+
if (this.canonicalTimelineRefreshPending) {
|
|
7754
|
+
this.canonicalTimelineRefreshPending = false;
|
|
7755
|
+
this.canonicalMessageRefreshIds.clear();
|
|
7756
|
+
chat?.refreshMessages();
|
|
7757
|
+
} else if (this.canonicalMessageRefreshIds.size > 0) {
|
|
7758
|
+
const messageIds = [...this.canonicalMessageRefreshIds];
|
|
7759
|
+
this.canonicalMessageRefreshIds.clear();
|
|
7760
|
+
chat?.refreshMessages(messageIds);
|
|
7761
|
+
}
|
|
6979
7762
|
if (
|
|
6980
7763
|
changedPropertiesArg.has('selectedProjectId')
|
|
6981
7764
|
|| changedPropertiesArg.has('selectedSessionId')
|
|
@@ -7928,6 +8711,417 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
7928
8711
|
}
|
|
7929
8712
|
}
|
|
7930
8713
|
|
|
8714
|
+
private liveMessageOrdersEqual(
|
|
8715
|
+
leftArg: interfaces.IControllerTranscriptOrder | undefined,
|
|
8716
|
+
rightArg: interfaces.IControllerTranscriptOrder | undefined,
|
|
8717
|
+
): boolean {
|
|
8718
|
+
return leftArg === undefined
|
|
8719
|
+
? rightArg === undefined
|
|
8720
|
+
: rightArg !== undefined
|
|
8721
|
+
&& leftArg.messageIndex === rightArg.messageIndex
|
|
8722
|
+
&& leftArg.partIndex === rightArg.partIndex;
|
|
8723
|
+
}
|
|
8724
|
+
|
|
8725
|
+
private liveMessageIdentityMatches(
|
|
8726
|
+
previousArg: interfaces.IControllerReasoningUpdate | interfaces.IControllerTextUpdate,
|
|
8727
|
+
deltaArg: interfaces.IControllerReasoningDelta | interfaces.IControllerTextDelta,
|
|
8728
|
+
): boolean {
|
|
8729
|
+
return controllerRuntimeIdsEqual(previousArg.sessionId, deltaArg.sessionId)
|
|
8730
|
+
&& controllerRuntimeIdsEqual(previousArg.messageId, deltaArg.messageId)
|
|
8731
|
+
&& controllerRuntimeIdsEqual(previousArg.partId, deltaArg.partId)
|
|
8732
|
+
&& this.liveMessageOrdersEqual(previousArg.order, deltaArg.order);
|
|
8733
|
+
}
|
|
8734
|
+
|
|
8735
|
+
private liveMessageEventPassesBarriers(
|
|
8736
|
+
projectIdArg: string,
|
|
8737
|
+
updateArg: interfaces.IControllerReasoningUpdate
|
|
8738
|
+
| interfaces.IControllerTextUpdate
|
|
8739
|
+
| interfaces.IControllerReasoningDelta
|
|
8740
|
+
| interfaces.IControllerTextDelta,
|
|
8741
|
+
): { accepted: boolean; epochAdvanced: boolean } {
|
|
8742
|
+
if (!isSessionRuntimeId(updateArg.sessionId)) {
|
|
8743
|
+
return { accepted: false, epochAdvanced: false };
|
|
8744
|
+
}
|
|
8745
|
+
if (
|
|
8746
|
+
updateArg.sourceUpdatedAt
|
|
8747
|
+
<= (this.liveHistoryBarriers.get(
|
|
8748
|
+
this.liveToolSessionKey(projectIdArg, updateArg.sessionId),
|
|
8749
|
+
) ?? Number.NEGATIVE_INFINITY)
|
|
8750
|
+
) return { accepted: false, epochAdvanced: false };
|
|
8751
|
+
const previousEpoch = this.liveMessageStreamEpochs.get(updateArg.sessionId.harnessId) ?? 0;
|
|
8752
|
+
const accepted = this.acceptLiveMessageEpoch(
|
|
8753
|
+
updateArg.sessionId.harnessId,
|
|
8754
|
+
updateArg.streamEpoch,
|
|
8755
|
+
true,
|
|
8756
|
+
false,
|
|
8757
|
+
);
|
|
8758
|
+
return { accepted, epochAdvanced: accepted && updateArg.streamEpoch > previousEpoch };
|
|
8759
|
+
}
|
|
8760
|
+
|
|
8761
|
+
private refreshAfterTerminalLiveMessage(
|
|
8762
|
+
projectIdArg: string,
|
|
8763
|
+
sessionIdArg: TBrowserSessionId,
|
|
8764
|
+
): void {
|
|
8765
|
+
if (
|
|
8766
|
+
projectIdArg !== this.selectedProjectId
|
|
8767
|
+
|| !controllerRuntimeIdsEqual(sessionIdArg, this.selectedSessionId)
|
|
8768
|
+
) return;
|
|
8769
|
+
void this.refreshSelectedSessionDetail();
|
|
8770
|
+
void this.refreshSessions(false);
|
|
8771
|
+
}
|
|
8772
|
+
|
|
8773
|
+
private applyLiveReasoningUpdate(
|
|
8774
|
+
projectIdArg: string,
|
|
8775
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
8776
|
+
): void {
|
|
8777
|
+
if (!isSessionRuntimeId(updateArg.sessionId)) return;
|
|
8778
|
+
const sessionId = updateArg.sessionId;
|
|
8779
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, updateArg);
|
|
8780
|
+
if (!barrier.accepted) return;
|
|
8781
|
+
if (controllerRuntimeIdsEqual(updateArg.sessionId, this.subagentModalSessionId)) {
|
|
8782
|
+
this.scheduleSubagentModalRefresh();
|
|
8783
|
+
}
|
|
8784
|
+
if (
|
|
8785
|
+
projectIdArg !== this.selectedProjectId
|
|
8786
|
+
|| !controllerRuntimeIdsEqual(updateArg.sessionId, this.selectedSessionId)
|
|
8787
|
+
) return;
|
|
8788
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
8789
|
+
this.liveToolSessionKey(projectIdArg, updateArg.sessionId),
|
|
8790
|
+
)?.cursor;
|
|
8791
|
+
if (
|
|
8792
|
+
floor
|
|
8793
|
+
&& floor.streamEpoch === updateArg.streamEpoch
|
|
8794
|
+
&& updateArg.revision <= floor.revision
|
|
8795
|
+
) return;
|
|
8796
|
+
const key = this.liveMessageUpdateKey(projectIdArg, updateArg);
|
|
8797
|
+
const previous = this.liveReasoningUpdates.get(key)
|
|
8798
|
+
?? this.liveReasoningBaselines.get(key)?.update;
|
|
8799
|
+
if (
|
|
8800
|
+
previous
|
|
8801
|
+
&& previous.streamEpoch === updateArg.streamEpoch
|
|
8802
|
+
&& updateArg.revision <= previous.revision
|
|
8803
|
+
) return;
|
|
8804
|
+
if (!this.storeLiveReasoningUpdate(key, updateArg)) {
|
|
8805
|
+
this.blockLiveMessageDeltas(
|
|
8806
|
+
projectIdArg,
|
|
8807
|
+
sessionId,
|
|
8808
|
+
updateArg.streamEpoch,
|
|
8809
|
+
!barrier.epochAdvanced,
|
|
8810
|
+
);
|
|
8811
|
+
return;
|
|
8812
|
+
}
|
|
8813
|
+
|
|
8814
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
8815
|
+
if (projection && this.canonicalTimelineRefreshPending) this.requestUpdate();
|
|
8816
|
+
const target = projection ? this.canonicalReasoningTarget(projection, updateArg) : undefined;
|
|
8817
|
+
if (!projection || !target) {
|
|
8818
|
+
this.requestCanonicalChatReconciliation();
|
|
8819
|
+
} else if (!this.harnessMessageIsTerminal(target.message)) {
|
|
8820
|
+
const previousStreaming = target.message.streaming === true;
|
|
8821
|
+
const previousText = target.part.text;
|
|
8822
|
+
let correction = !updateArg.text.startsWith(previousText);
|
|
8823
|
+
if (!correction && updateArg.text.length > previousText.length) {
|
|
8824
|
+
correction = !this.applyCanonicalReasoningDelta(
|
|
8825
|
+
projection,
|
|
8826
|
+
target,
|
|
8827
|
+
updateArg.text.slice(previousText.length),
|
|
8828
|
+
);
|
|
8829
|
+
}
|
|
8830
|
+
if (correction) target.part.text = updateArg.text;
|
|
8831
|
+
target.part.startedAt = updateArg.sourceUpdatedAt;
|
|
8832
|
+
if (updateArg.status === 'running') delete target.part.endedAt;
|
|
8833
|
+
target.message.streaming = updateArg.status === 'running'
|
|
8834
|
+
|| (updateArg.sessionId.harnessId === 'opencode' && previousStreaming);
|
|
8835
|
+
if (
|
|
8836
|
+
!correction
|
|
8837
|
+
&& updateArg.status === 'running'
|
|
8838
|
+
&& !previousStreaming
|
|
8839
|
+
&& updateArg.text === previousText
|
|
8840
|
+
) {
|
|
8841
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
8842
|
+
type: 'reasoning',
|
|
8843
|
+
messageId: target.message.id,
|
|
8844
|
+
partId: target.part.id,
|
|
8845
|
+
delta: '',
|
|
8846
|
+
});
|
|
8847
|
+
}
|
|
8848
|
+
if (updateArg.status !== 'running') {
|
|
8849
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
8850
|
+
type: 'reasoning-end',
|
|
8851
|
+
messageId: target.message.id,
|
|
8852
|
+
partId: target.part.id,
|
|
8853
|
+
endedAt: updateArg.sourceUpdatedAt,
|
|
8854
|
+
});
|
|
8855
|
+
target.part.endedAt = updateArg.sourceUpdatedAt;
|
|
8856
|
+
}
|
|
8857
|
+
if (correction) {
|
|
8858
|
+
this.canonicalMessageRefreshIds.add(target.message.id);
|
|
8859
|
+
this.requestCanonicalChatReconciliation(false);
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8862
|
+
if (updateArg.status !== 'running') {
|
|
8863
|
+
this.refreshAfterTerminalLiveMessage(projectIdArg, sessionId);
|
|
8864
|
+
}
|
|
8865
|
+
}
|
|
8866
|
+
|
|
8867
|
+
private applyLiveTextUpdate(
|
|
8868
|
+
projectIdArg: string,
|
|
8869
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
8870
|
+
): void {
|
|
8871
|
+
if (!isSessionRuntimeId(updateArg.sessionId)) return;
|
|
8872
|
+
const sessionId = updateArg.sessionId;
|
|
8873
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, updateArg);
|
|
8874
|
+
if (!barrier.accepted) return;
|
|
8875
|
+
if (controllerRuntimeIdsEqual(updateArg.sessionId, this.subagentModalSessionId)) {
|
|
8876
|
+
this.scheduleSubagentModalRefresh();
|
|
8877
|
+
}
|
|
8878
|
+
if (
|
|
8879
|
+
projectIdArg !== this.selectedProjectId
|
|
8880
|
+
|| !controllerRuntimeIdsEqual(updateArg.sessionId, this.selectedSessionId)
|
|
8881
|
+
) return;
|
|
8882
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
8883
|
+
this.liveToolSessionKey(projectIdArg, updateArg.sessionId),
|
|
8884
|
+
)?.cursor;
|
|
8885
|
+
if (
|
|
8886
|
+
floor
|
|
8887
|
+
&& floor.streamEpoch === updateArg.streamEpoch
|
|
8888
|
+
&& updateArg.revision <= floor.revision
|
|
8889
|
+
) return;
|
|
8890
|
+
const key = this.liveMessageUpdateKey(projectIdArg, updateArg);
|
|
8891
|
+
const previous = this.liveTextUpdates.get(key) ?? this.liveTextBaselines.get(key)?.update;
|
|
8892
|
+
if (
|
|
8893
|
+
previous
|
|
8894
|
+
&& previous.streamEpoch === updateArg.streamEpoch
|
|
8895
|
+
&& updateArg.revision <= previous.revision
|
|
8896
|
+
) return;
|
|
8897
|
+
if (!this.storeLiveTextUpdate(key, updateArg)) {
|
|
8898
|
+
this.blockLiveMessageDeltas(
|
|
8899
|
+
projectIdArg,
|
|
8900
|
+
sessionId,
|
|
8901
|
+
updateArg.streamEpoch,
|
|
8902
|
+
!barrier.epochAdvanced,
|
|
8903
|
+
);
|
|
8904
|
+
return;
|
|
8905
|
+
}
|
|
8906
|
+
|
|
8907
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
8908
|
+
if (projection && this.canonicalTimelineRefreshPending) this.requestUpdate();
|
|
8909
|
+
const target = projection ? this.canonicalTextTarget(projection, updateArg) : undefined;
|
|
8910
|
+
if (!projection || !target) {
|
|
8911
|
+
this.requestCanonicalChatReconciliation();
|
|
8912
|
+
} else if (!this.harnessMessageIsTerminal(target)) {
|
|
8913
|
+
const previousStreaming = target.streaming === true;
|
|
8914
|
+
const previousText = target.text;
|
|
8915
|
+
let correction = !updateArg.text.startsWith(previousText);
|
|
8916
|
+
if (!correction && updateArg.text.length > previousText.length) {
|
|
8917
|
+
correction = !this.applyCanonicalTextDelta(
|
|
8918
|
+
projection,
|
|
8919
|
+
target,
|
|
8920
|
+
updateArg.text.slice(previousText.length),
|
|
8921
|
+
);
|
|
8922
|
+
}
|
|
8923
|
+
if (correction) target.text = updateArg.text;
|
|
8924
|
+
if (updateArg.status === 'running') {
|
|
8925
|
+
target.streaming = true;
|
|
8926
|
+
if (!correction && !previousStreaming && updateArg.text === previousText) {
|
|
8927
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
8928
|
+
type: 'text',
|
|
8929
|
+
messageId: target.id,
|
|
8930
|
+
delta: '',
|
|
8931
|
+
});
|
|
8932
|
+
}
|
|
8933
|
+
} else {
|
|
8934
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
8935
|
+
type: 'message-end',
|
|
8936
|
+
messageId: target.id,
|
|
8937
|
+
});
|
|
8938
|
+
target.streaming = false;
|
|
8939
|
+
target.updatedAt = updateArg.sourceUpdatedAt;
|
|
8940
|
+
}
|
|
8941
|
+
if (correction) {
|
|
8942
|
+
this.canonicalMessageRefreshIds.add(target.id);
|
|
8943
|
+
this.requestCanonicalChatReconciliation(false);
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
if (updateArg.status === 'completed') {
|
|
8947
|
+
this.refreshAfterTerminalLiveMessage(projectIdArg, sessionId);
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
|
|
8951
|
+
private applyLiveReasoningDelta(
|
|
8952
|
+
projectIdArg: string,
|
|
8953
|
+
deltaArg: interfaces.IControllerReasoningDelta,
|
|
8954
|
+
): void {
|
|
8955
|
+
if (!isSessionRuntimeId(deltaArg.sessionId)) return;
|
|
8956
|
+
const sessionId = deltaArg.sessionId;
|
|
8957
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, deltaArg);
|
|
8958
|
+
if (!barrier.accepted) return;
|
|
8959
|
+
if (
|
|
8960
|
+
projectIdArg !== this.selectedProjectId
|
|
8961
|
+
|| !controllerRuntimeIdsEqual(deltaArg.sessionId, this.selectedSessionId)
|
|
8962
|
+
|| this.liveMessageDeltasBlocked(projectIdArg, sessionId, deltaArg.streamEpoch)
|
|
8963
|
+
) return;
|
|
8964
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
8965
|
+
this.liveToolSessionKey(projectIdArg, deltaArg.sessionId),
|
|
8966
|
+
)?.cursor;
|
|
8967
|
+
if (
|
|
8968
|
+
floor
|
|
8969
|
+
&& floor.streamEpoch === deltaArg.streamEpoch
|
|
8970
|
+
&& deltaArg.revision <= floor.revision
|
|
8971
|
+
) return;
|
|
8972
|
+
const key = this.liveMessageUpdateKey(projectIdArg, deltaArg);
|
|
8973
|
+
const active = this.liveReasoningUpdates.get(key);
|
|
8974
|
+
const baseline = this.liveReasoningBaselines.get(key);
|
|
8975
|
+
const previous = active ?? baseline?.update;
|
|
8976
|
+
if (
|
|
8977
|
+
previous
|
|
8978
|
+
&& previous.streamEpoch === deltaArg.streamEpoch
|
|
8979
|
+
&& deltaArg.revision <= previous.revision
|
|
8980
|
+
) return;
|
|
8981
|
+
const textUtf8Bytes = active
|
|
8982
|
+
? this.liveReasoningTextUtf8Bytes.get(key)
|
|
8983
|
+
?? browserTextEncoder.encode(active.text).byteLength
|
|
8984
|
+
: baseline?.textUtf8Bytes;
|
|
8985
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
8986
|
+
const target = projection ? this.canonicalReasoningTarget(projection, deltaArg) : undefined;
|
|
8987
|
+
const nextTextUtf8Bytes = previous && textUtf8Bytes !== undefined
|
|
8988
|
+
? controllerTextAfterAppendUtf8Bytes(previous.text, textUtf8Bytes, deltaArg.delta)
|
|
8989
|
+
: undefined;
|
|
8990
|
+
if (
|
|
8991
|
+
!previous
|
|
8992
|
+
|| previous.status !== 'running'
|
|
8993
|
+
|| previous.streamEpoch !== deltaArg.streamEpoch
|
|
8994
|
+
|| deltaArg.revision <= previous.revision
|
|
8995
|
+
|| deltaArg.sourceUpdatedAt < previous.sourceUpdatedAt
|
|
8996
|
+
|| !this.liveMessageIdentityMatches(previous, deltaArg)
|
|
8997
|
+
|| textUtf8Bytes !== deltaArg.baseTextUtf8Bytes
|
|
8998
|
+
|| nextTextUtf8Bytes !== deltaArg.textUtf8Bytes
|
|
8999
|
+
|| !projection
|
|
9000
|
+
|| !target
|
|
9001
|
+
|| this.harnessMessageIsTerminal(target.message)
|
|
9002
|
+
|| target.part.text !== previous.text
|
|
9003
|
+
|| (deltaArg.order !== undefined
|
|
9004
|
+
&& (!target.message.order
|
|
9005
|
+
|| transcriptOrderKey(target.message.order) !== transcriptOrderKey(deltaArg.order)))
|
|
9006
|
+
) {
|
|
9007
|
+
this.blockLiveMessageDeltas(
|
|
9008
|
+
projectIdArg,
|
|
9009
|
+
sessionId,
|
|
9010
|
+
deltaArg.streamEpoch,
|
|
9011
|
+
!barrier.epochAdvanced,
|
|
9012
|
+
);
|
|
9013
|
+
return;
|
|
9014
|
+
}
|
|
9015
|
+
if (!this.applyCanonicalReasoningDelta(projection, target, deltaArg.delta)) {
|
|
9016
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9017
|
+
return;
|
|
9018
|
+
}
|
|
9019
|
+
target.message.streaming = true;
|
|
9020
|
+
const update: interfaces.IControllerReasoningUpdate = {
|
|
9021
|
+
...previous,
|
|
9022
|
+
text: `${previous.text}${deltaArg.delta}`,
|
|
9023
|
+
sourceUpdatedAt: deltaArg.sourceUpdatedAt,
|
|
9024
|
+
revision: deltaArg.revision,
|
|
9025
|
+
streamEpoch: deltaArg.streamEpoch,
|
|
9026
|
+
};
|
|
9027
|
+
if (!this.storeLiveReasoningDeltaState(
|
|
9028
|
+
key,
|
|
9029
|
+
update,
|
|
9030
|
+
deltaArg.textUtf8Bytes,
|
|
9031
|
+
deltaArg.delta,
|
|
9032
|
+
)) {
|
|
9033
|
+
target.part.text = previous.text;
|
|
9034
|
+
this.requestCanonicalChatReconciliation();
|
|
9035
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9036
|
+
}
|
|
9037
|
+
}
|
|
9038
|
+
|
|
9039
|
+
private applyLiveTextDelta(
|
|
9040
|
+
projectIdArg: string,
|
|
9041
|
+
deltaArg: interfaces.IControllerTextDelta,
|
|
9042
|
+
): void {
|
|
9043
|
+
if (!isSessionRuntimeId(deltaArg.sessionId)) return;
|
|
9044
|
+
const sessionId = deltaArg.sessionId;
|
|
9045
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, deltaArg);
|
|
9046
|
+
if (!barrier.accepted) return;
|
|
9047
|
+
if (
|
|
9048
|
+
projectIdArg !== this.selectedProjectId
|
|
9049
|
+
|| !controllerRuntimeIdsEqual(deltaArg.sessionId, this.selectedSessionId)
|
|
9050
|
+
|| this.liveMessageDeltasBlocked(projectIdArg, sessionId, deltaArg.streamEpoch)
|
|
9051
|
+
) return;
|
|
9052
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
9053
|
+
this.liveToolSessionKey(projectIdArg, deltaArg.sessionId),
|
|
9054
|
+
)?.cursor;
|
|
9055
|
+
if (
|
|
9056
|
+
floor
|
|
9057
|
+
&& floor.streamEpoch === deltaArg.streamEpoch
|
|
9058
|
+
&& deltaArg.revision <= floor.revision
|
|
9059
|
+
) return;
|
|
9060
|
+
const key = this.liveMessageUpdateKey(projectIdArg, deltaArg);
|
|
9061
|
+
const active = this.liveTextUpdates.get(key);
|
|
9062
|
+
const baseline = this.liveTextBaselines.get(key);
|
|
9063
|
+
const previous = active ?? baseline?.update;
|
|
9064
|
+
if (
|
|
9065
|
+
previous
|
|
9066
|
+
&& previous.streamEpoch === deltaArg.streamEpoch
|
|
9067
|
+
&& deltaArg.revision <= previous.revision
|
|
9068
|
+
) return;
|
|
9069
|
+
const textUtf8Bytes = active
|
|
9070
|
+
? this.liveTextTextUtf8Bytes.get(key)
|
|
9071
|
+
?? browserTextEncoder.encode(active.text).byteLength
|
|
9072
|
+
: baseline?.textUtf8Bytes;
|
|
9073
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
9074
|
+
const target = projection ? this.canonicalTextTarget(projection, deltaArg) : undefined;
|
|
9075
|
+
const nextTextUtf8Bytes = previous && textUtf8Bytes !== undefined
|
|
9076
|
+
? controllerTextAfterAppendUtf8Bytes(previous.text, textUtf8Bytes, deltaArg.delta)
|
|
9077
|
+
: undefined;
|
|
9078
|
+
if (
|
|
9079
|
+
!previous
|
|
9080
|
+
|| previous.status !== 'running'
|
|
9081
|
+
|| previous.streamEpoch !== deltaArg.streamEpoch
|
|
9082
|
+
|| deltaArg.revision <= previous.revision
|
|
9083
|
+
|| deltaArg.sourceUpdatedAt < previous.sourceUpdatedAt
|
|
9084
|
+
|| !this.liveMessageIdentityMatches(previous, deltaArg)
|
|
9085
|
+
|| textUtf8Bytes !== deltaArg.baseTextUtf8Bytes
|
|
9086
|
+
|| nextTextUtf8Bytes !== deltaArg.textUtf8Bytes
|
|
9087
|
+
|| !projection
|
|
9088
|
+
|| !target
|
|
9089
|
+
|| this.harnessMessageIsTerminal(target)
|
|
9090
|
+
|| target.text !== previous.text
|
|
9091
|
+
|| (deltaArg.order !== undefined
|
|
9092
|
+
&& (!target.order || transcriptOrderKey(target.order) !== transcriptOrderKey(deltaArg.order)))
|
|
9093
|
+
) {
|
|
9094
|
+
this.blockLiveMessageDeltas(
|
|
9095
|
+
projectIdArg,
|
|
9096
|
+
sessionId,
|
|
9097
|
+
deltaArg.streamEpoch,
|
|
9098
|
+
!barrier.epochAdvanced,
|
|
9099
|
+
);
|
|
9100
|
+
return;
|
|
9101
|
+
}
|
|
9102
|
+
if (!this.applyCanonicalTextDelta(projection, target, deltaArg.delta)) {
|
|
9103
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
const update: interfaces.IControllerTextUpdate = {
|
|
9107
|
+
...previous,
|
|
9108
|
+
text: `${previous.text}${deltaArg.delta}`,
|
|
9109
|
+
sourceUpdatedAt: deltaArg.sourceUpdatedAt,
|
|
9110
|
+
revision: deltaArg.revision,
|
|
9111
|
+
streamEpoch: deltaArg.streamEpoch,
|
|
9112
|
+
};
|
|
9113
|
+
if (!this.storeLiveTextDeltaState(
|
|
9114
|
+
key,
|
|
9115
|
+
update,
|
|
9116
|
+
deltaArg.textUtf8Bytes,
|
|
9117
|
+
deltaArg.delta,
|
|
9118
|
+
)) {
|
|
9119
|
+
target.text = previous.text;
|
|
9120
|
+
this.requestCanonicalChatReconciliation();
|
|
9121
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9122
|
+
}
|
|
9123
|
+
}
|
|
9124
|
+
|
|
7931
9125
|
private async handleControllerEvent(eventArg: interfaces.IControllerEvent): Promise<void> {
|
|
7932
9126
|
if (!this.authenticated) {
|
|
7933
9127
|
return;
|
|
@@ -8033,79 +9227,27 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
8033
9227
|
return;
|
|
8034
9228
|
}
|
|
8035
9229
|
if (eventArg.type === 'session.reasoning.updated' && eventArg.reasoningUpdate) {
|
|
8036
|
-
const update = eventArg.reasoningUpdate;
|
|
8037
9230
|
if (typeof eventArg.projectId !== 'string') return;
|
|
8038
|
-
if (!isSessionRuntimeId(
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
) return;
|
|
8045
|
-
|
|
8046
|
-
if (controllerRuntimeIdsEqual(update.sessionId, this.subagentModalSessionId)) {
|
|
8047
|
-
this.scheduleSubagentModalRefresh();
|
|
8048
|
-
}
|
|
8049
|
-
if (
|
|
8050
|
-
eventArg.projectId !== this.selectedProjectId
|
|
8051
|
-
|| !controllerRuntimeIdsEqual(update.sessionId, this.selectedSessionId)
|
|
8052
|
-
) return;
|
|
8053
|
-
const floor = this.liveMessageHydrationFloors.get(
|
|
8054
|
-
this.liveToolSessionKey(eventArg.projectId, update.sessionId),
|
|
8055
|
-
)?.cursor;
|
|
8056
|
-
if (
|
|
8057
|
-
floor
|
|
8058
|
-
&& floor.streamEpoch === update.streamEpoch
|
|
8059
|
-
&& update.revision <= floor.revision
|
|
8060
|
-
) return;
|
|
8061
|
-
const key = `${eventArg.projectId ?? ''}\0${controllerRuntimeIdToUiKey(update.sessionId)}\0${controllerRuntimeIdToUiKey(update.partId)}`;
|
|
8062
|
-
const previous = this.liveReasoningUpdates.get(key);
|
|
8063
|
-
if (
|
|
8064
|
-
!previous
|
|
8065
|
-
|| update.streamEpoch > previous.streamEpoch
|
|
8066
|
-
|| (update.streamEpoch === previous.streamEpoch && update.revision > previous.revision)
|
|
8067
|
-
) {
|
|
8068
|
-
if (this.storeLiveReasoningUpdate(key, update)) this.requestUpdate();
|
|
8069
|
-
}
|
|
8070
|
-
if (update.status !== 'running') this.refreshSessionsNow();
|
|
9231
|
+
if (!isSessionRuntimeId(eventArg.reasoningUpdate.sessionId)) return;
|
|
9232
|
+
this.applyLiveReasoningUpdate(eventArg.projectId, eventArg.reasoningUpdate);
|
|
9233
|
+
return;
|
|
9234
|
+
}
|
|
9235
|
+
if (eventArg.type === 'session.reasoning.delta' && eventArg.reasoningDelta) {
|
|
9236
|
+
if (typeof eventArg.projectId !== 'string') return;
|
|
9237
|
+
if (!isSessionRuntimeId(eventArg.reasoningDelta.sessionId)) return;
|
|
9238
|
+
this.applyLiveReasoningDelta(eventArg.projectId, eventArg.reasoningDelta);
|
|
8071
9239
|
return;
|
|
8072
9240
|
}
|
|
8073
9241
|
if (eventArg.type === 'session.text.updated' && eventArg.textUpdate) {
|
|
8074
|
-
const update = eventArg.textUpdate;
|
|
8075
9242
|
if (typeof eventArg.projectId !== 'string') return;
|
|
8076
|
-
if (!isSessionRuntimeId(
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
) return;
|
|
8083
|
-
|
|
8084
|
-
if (controllerRuntimeIdsEqual(update.sessionId, this.subagentModalSessionId)) {
|
|
8085
|
-
this.scheduleSubagentModalRefresh();
|
|
8086
|
-
}
|
|
8087
|
-
if (
|
|
8088
|
-
eventArg.projectId !== this.selectedProjectId
|
|
8089
|
-
|| !controllerRuntimeIdsEqual(update.sessionId, this.selectedSessionId)
|
|
8090
|
-
) return;
|
|
8091
|
-
const floor = this.liveMessageHydrationFloors.get(
|
|
8092
|
-
this.liveToolSessionKey(eventArg.projectId, update.sessionId),
|
|
8093
|
-
)?.cursor;
|
|
8094
|
-
if (
|
|
8095
|
-
floor
|
|
8096
|
-
&& floor.streamEpoch === update.streamEpoch
|
|
8097
|
-
&& update.revision <= floor.revision
|
|
8098
|
-
) return;
|
|
8099
|
-
const key = `${eventArg.projectId ?? ''}\0${controllerRuntimeIdToUiKey(update.sessionId)}\0${controllerRuntimeIdToUiKey(update.partId)}`;
|
|
8100
|
-
const previous = this.liveTextUpdates.get(key);
|
|
8101
|
-
if (
|
|
8102
|
-
!previous
|
|
8103
|
-
|| update.streamEpoch > previous.streamEpoch
|
|
8104
|
-
|| (update.streamEpoch === previous.streamEpoch && update.revision > previous.revision)
|
|
8105
|
-
) {
|
|
8106
|
-
if (this.storeLiveTextUpdate(key, update)) this.requestUpdate();
|
|
8107
|
-
}
|
|
8108
|
-
if (update.status === 'completed') this.refreshSessionsNow();
|
|
9243
|
+
if (!isSessionRuntimeId(eventArg.textUpdate.sessionId)) return;
|
|
9244
|
+
this.applyLiveTextUpdate(eventArg.projectId, eventArg.textUpdate);
|
|
9245
|
+
return;
|
|
9246
|
+
}
|
|
9247
|
+
if (eventArg.type === 'session.text.delta' && eventArg.textDelta) {
|
|
9248
|
+
if (typeof eventArg.projectId !== 'string') return;
|
|
9249
|
+
if (!isSessionRuntimeId(eventArg.textDelta.sessionId)) return;
|
|
9250
|
+
this.applyLiveTextDelta(eventArg.projectId, eventArg.textDelta);
|
|
8109
9251
|
return;
|
|
8110
9252
|
}
|
|
8111
9253
|
this.applySessionEventState(eventArg);
|
|
@@ -10419,25 +11561,6 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10419
11561
|
<dees-badge type="success" text="Connected" .rounded=${true}></dees-badge>
|
|
10420
11562
|
<span class="headerSpacer"></span>
|
|
10421
11563
|
<div class="headerActions">
|
|
10422
|
-
<dees-button
|
|
10423
|
-
class="refreshAction"
|
|
10424
|
-
type="ghost"
|
|
10425
|
-
size="sm"
|
|
10426
|
-
?disabled=${this.sessionsLoading || this.mutationPending}
|
|
10427
|
-
@clicked=${() => void this.refreshSessions()}
|
|
10428
|
-
>
|
|
10429
|
-
<dees-icon icon="lucide:RefreshCw" iconSize="14"></dees-icon>
|
|
10430
|
-
Refresh
|
|
10431
|
-
</dees-button>
|
|
10432
|
-
<dees-button
|
|
10433
|
-
type="ghost"
|
|
10434
|
-
size="sm"
|
|
10435
|
-
?disabled=${!this.selectedSessionId || this.mutationPending}
|
|
10436
|
-
@clicked=${this.showDeleteConfirmation}
|
|
10437
|
-
>
|
|
10438
|
-
<dees-icon icon="lucide:Trash2" iconSize="14"></dees-icon>
|
|
10439
|
-
Delete
|
|
10440
|
-
</dees-button>
|
|
10441
11564
|
<dees-button
|
|
10442
11565
|
type="ghost"
|
|
10443
11566
|
size="sm"
|
|
@@ -10798,6 +11921,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10798
11921
|
.reasoningEffort=${this.resolveSessionEffortString() || 'default'}
|
|
10799
11922
|
.effortOptions=${this.resolveSessionEffortOptions()}
|
|
10800
11923
|
.showTodosPanel=${false}
|
|
11924
|
+
.markdownWhileStreaming=${false}
|
|
10801
11925
|
heading=${`New ${sessionHarnessLabel(this.draftHarnessId)} conversation`}
|
|
10802
11926
|
subheading=${directory
|
|
10803
11927
|
? `${sessionHarnessLabel(this.draftHarnessId)} creates this session on first send · starts in ${directory}`
|
|
@@ -10871,27 +11995,12 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10871
11995
|
const sessionId = detailArg.session.id;
|
|
10872
11996
|
const isOpenCode = sessionId.harnessId === 'opencode';
|
|
10873
11997
|
const readOnly = this.sessionIsReadOnly(sessionId);
|
|
10874
|
-
// The
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
.
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
for (const prompt of detailArg.pendingPrompts) {
|
|
10881
|
-
const id = controllerRuntimeIdToUiKey(prompt.id);
|
|
10882
|
-
if (visibleMessageIds.has(id)) continue;
|
|
10883
|
-
messages.push({
|
|
10884
|
-
id,
|
|
10885
|
-
role: 'user',
|
|
10886
|
-
text: prompt.text,
|
|
10887
|
-
createdAt: prompt.createdAt,
|
|
10888
|
-
markdown: false,
|
|
10889
|
-
attachments: prompt.attachments.map((attachment) => ({ ...attachment })),
|
|
10890
|
-
});
|
|
10891
|
-
}
|
|
10892
|
-
this.mergeLiveReasoningMessages(messages, this.selectedProjectId, sessionId);
|
|
10893
|
-
this.mergeLiveTextMessages(messages, this.selectedProjectId, sessionId);
|
|
10894
|
-
this.mergeLiveToolMessages(messages, this.selectedProjectId, sessionId);
|
|
11998
|
+
// The canonical array and all delta targets remain stable across unrelated app renders.
|
|
11999
|
+
const messages = this.ensureCanonicalChatProjection(
|
|
12000
|
+
detailArg,
|
|
12001
|
+
this.selectedProjectId,
|
|
12002
|
+
sessionId,
|
|
12003
|
+
).messages;
|
|
10895
12004
|
const status = this.toHarnessStatus(
|
|
10896
12005
|
detailArg.session,
|
|
10897
12006
|
this.activeSubagentCount(messages),
|
|
@@ -11089,12 +12198,14 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
11089
12198
|
.intelligenceBusy=${this.intelligenceAskIdsBySession.has(operationKey)
|
|
11090
12199
|
|| detailArg.intelligenceExchanges.some((exchange) => exchange.status === 'running')}
|
|
11091
12200
|
.intelligenceError=${intelligenceError}
|
|
11092
|
-
.todos=${
|
|
12201
|
+
.todos=${detailArg.todos.map((todoArg) => ({
|
|
11093
12202
|
...todoArg,
|
|
11094
12203
|
...(todoArg.id ? { id: controllerRuntimeIdToUiKey(todoArg.id) } : {}),
|
|
11095
|
-
}))
|
|
12204
|
+
}))}
|
|
12205
|
+
.todosAuthoritative=${true}
|
|
11096
12206
|
.busy=${detailArg.session.status === 'busy' || detailArg.session.status === 'retry'}
|
|
11097
12207
|
.queuedCount=${detailArg.pendingPrompts.length}
|
|
12208
|
+
.steeringEnabled=${false}
|
|
11098
12209
|
.disabled=${this.detailLoading
|
|
11099
12210
|
|| this.mutationPending
|
|
11100
12211
|
|| this.sessionModelPending
|
|
@@ -11112,7 +12223,8 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
11112
12223
|
.modelOptions=${this.modelOptionsForHarness(sessionId.harnessId, account)}
|
|
11113
12224
|
.reasoningEffort=${this.resolveSessionEffortString(sessionId) || 'default'}
|
|
11114
12225
|
.effortOptions=${this.resolveSessionEffortOptions(sessionId)}
|
|
11115
|
-
.showTodosPanel=${
|
|
12226
|
+
.showTodosPanel=${true}
|
|
12227
|
+
.markdownWhileStreaming=${false}
|
|
11116
12228
|
heading=${detailArg.session.title || `${sessionHarnessLabel(sessionId.harnessId)} session`}
|
|
11117
12229
|
subheading=${readOnly
|
|
11118
12230
|
? `Flex unavailable · saved projection · read-only · ${runtimeIdDisplay(sessionId)}`
|