agl 22.0.4 → 24.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 +21 -0
- package/dist_serve/bundle.js +2418 -2408
- package/dist_serve/bundle.js.map +1 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.authstore.js +2 -2
- package/dist_ts/classes.browserresourcehost.d.ts +1 -0
- package/dist_ts/classes.browserresourcehost.js +7 -1
- package/dist_ts/classes.controller.d.ts +37 -5
- package/dist_ts/classes.controller.js +1491 -249
- 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/classes.validation.d.ts +9 -1
- package/dist_ts/classes.validation.js +62 -7
- package/dist_ts/interfaces.flexipc.d.ts +18 -1
- package/dist_ts/interfaces.flexipc.js +42 -8
- package/dist_ts_interfaces/browsertransport.d.ts +72 -0
- package/dist_ts_interfaces/browsertransport.js +47 -0
- package/dist_ts_interfaces/index.d.ts +1 -0
- package/dist_ts_interfaces/index.js +2 -1
- package/dist_ts_interfaces/interfaces.d.ts +42 -7
- package/dist_ts_interfaces/interfaces.js +5 -3
- package/dist_ts_interfaces/requests.d.ts +20 -21
- package/package.json +7 -7
- package/readme.md +12 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.authstore.ts +1 -1
- package/ts/classes.browserresourcehost.ts +7 -0
- package/ts/classes.controller.ts +1960 -305
- package/ts/classes.flexservice.ts +132 -2
- package/ts/classes.opencodeclient.ts +19 -0
- package/ts/classes.validation.ts +77 -6
- package/ts/interfaces.flexipc.ts +65 -7
- package/ts_interfaces/browsertransport.ts +126 -0
- package/ts_interfaces/index.ts +1 -0
- package/ts_interfaces/interfaces.ts +46 -11
- package/ts_interfaces/requests.ts +23 -30
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/classes.browserviewtransport.ts +532 -0
- package/ts_web/classes.controllersocketclient.ts +119 -29
- package/ts_web/elements.harnesscontrollerapp.ts +1727 -287
- package/ts_web/index.ts +1 -0
- package/ts_web/plugins.ts +2 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import * as interfaces from '../ts_interfaces/index.js';
|
|
3
3
|
import { commitinfo } from './00_commitinfo_data.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
type TControllerBrowserViewEventMessage,
|
|
7
|
-
} from './classes.controllersocketclient.js';
|
|
4
|
+
import { ControllerSocketClient } from './classes.controllersocketclient.js';
|
|
5
|
+
import { ControllerBrowserViewTransportClient } from './classes.browserviewtransport.js';
|
|
8
6
|
import {
|
|
9
7
|
SessionDraftSync,
|
|
10
8
|
type ISessionDraftSyncState,
|
|
@@ -128,6 +126,33 @@ interface IDetailLoadOwnership {
|
|
|
128
126
|
messageStreamEpoch: number;
|
|
129
127
|
}
|
|
130
128
|
|
|
129
|
+
interface ILiveMessageBaseline<
|
|
130
|
+
TUpdate extends interfaces.IControllerReasoningUpdate | interfaces.IControllerTextUpdate,
|
|
131
|
+
> {
|
|
132
|
+
update: TUpdate;
|
|
133
|
+
textUtf8Bytes: number;
|
|
134
|
+
bytes: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface ICanonicalReasoningTarget {
|
|
138
|
+
message: plugins.deesCatalog.IHarnessMessage;
|
|
139
|
+
part: plugins.deesCatalog.IHarnessReasoningPart;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface ICanonicalChatProjection {
|
|
143
|
+
key: string;
|
|
144
|
+
sourceDetail: IControllerSessionRenderDetail | undefined;
|
|
145
|
+
messages: plugins.deesCatalog.IHarnessMessage[];
|
|
146
|
+
messagesById: Map<string, plugins.deesCatalog.IHarnessMessage>;
|
|
147
|
+
messagesByOrder: Map<string, plugins.deesCatalog.IHarnessMessage>;
|
|
148
|
+
reasoningById: Map<string, ICanonicalReasoningTarget>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface ILiveMessageDeltaBlock {
|
|
152
|
+
sessionId: TBrowserSessionId;
|
|
153
|
+
streamEpoch: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
131
156
|
interface ISlashCatalogOwnership {
|
|
132
157
|
key: string;
|
|
133
158
|
projectId: string;
|
|
@@ -282,6 +307,57 @@ const controllerJsonValuesEqual = (leftArg: unknown, rightArg: unknown): boolean
|
|
|
282
307
|
));
|
|
283
308
|
};
|
|
284
309
|
|
|
310
|
+
const reconcileKeyedJsonArray = <TValue>(
|
|
311
|
+
currentArg: readonly TValue[],
|
|
312
|
+
nextArg: readonly TValue[],
|
|
313
|
+
keyForArg: (valueArg: TValue, indexArg: number) => string,
|
|
314
|
+
): TValue[] => {
|
|
315
|
+
const currentByKey = new Map(
|
|
316
|
+
currentArg.map((value, index) => [keyForArg(value, index), value]),
|
|
317
|
+
);
|
|
318
|
+
const seenKeys = new Set<string>();
|
|
319
|
+
let unchanged = currentArg.length === nextArg.length;
|
|
320
|
+
const reconciled = nextArg.map((nextValue, index) => {
|
|
321
|
+
const key = keyForArg(nextValue, index);
|
|
322
|
+
const currentValue = seenKeys.has(key) ? undefined : currentByKey.get(key);
|
|
323
|
+
seenKeys.add(key);
|
|
324
|
+
const value = currentValue !== undefined && controllerJsonValuesEqual(currentValue, nextValue)
|
|
325
|
+
? currentValue
|
|
326
|
+
: nextValue;
|
|
327
|
+
unchanged &&= value === currentArg[index];
|
|
328
|
+
return value;
|
|
329
|
+
});
|
|
330
|
+
return unchanged ? currentArg as TValue[] : reconciled;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const browserTextEncoder = new TextEncoder();
|
|
334
|
+
|
|
335
|
+
const controllerTextAfterAppendUtf8Bytes = (
|
|
336
|
+
textArg: string,
|
|
337
|
+
textUtf8BytesArg: number,
|
|
338
|
+
deltaArg: string,
|
|
339
|
+
): number | undefined => {
|
|
340
|
+
const previousCodeUnit = textArg.charCodeAt(textArg.length - 1);
|
|
341
|
+
const nextCodeUnit = deltaArg.charCodeAt(0);
|
|
342
|
+
// Separately encoded surrogate halves cost six bytes; the joined pair costs four.
|
|
343
|
+
const boundaryCorrection = textArg.length > 0
|
|
344
|
+
&& deltaArg.length > 0
|
|
345
|
+
&& previousCodeUnit >= 0xD800
|
|
346
|
+
&& previousCodeUnit <= 0xDBFF
|
|
347
|
+
&& nextCodeUnit >= 0xDC00
|
|
348
|
+
&& nextCodeUnit <= 0xDFFF
|
|
349
|
+
? -2
|
|
350
|
+
: 0;
|
|
351
|
+
const result = textUtf8BytesArg
|
|
352
|
+
+ browserTextEncoder.encode(deltaArg).byteLength
|
|
353
|
+
+ boundaryCorrection;
|
|
354
|
+
return Number.isSafeInteger(result) && result >= 0 ? result : undefined;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const transcriptOrderKey = (orderArg: interfaces.IControllerTranscriptOrder): string => (
|
|
358
|
+
`${orderArg.messageIndex}\0${orderArg.partIndex}`
|
|
359
|
+
);
|
|
360
|
+
|
|
285
361
|
const controllerNormalizeBrowserAddress = (addressArg: string): string => {
|
|
286
362
|
const address = addressArg.trim();
|
|
287
363
|
if (!address) throw new Error('Enter a browser address.');
|
|
@@ -381,6 +457,7 @@ const layoutRequestTimeoutMs = 10_000;
|
|
|
381
457
|
const providerLoginPollTimeoutMs = 15 * 60 * 1000;
|
|
382
458
|
const providerRefreshPollTimeoutMs = 5 * 60 * 1000;
|
|
383
459
|
const providerOpenCodeSwitchTimeoutMs = 6 * 60 * 1000;
|
|
460
|
+
const browserRendererLifecycleTimeoutMs = 1_000;
|
|
384
461
|
const maximumSlashSuggestions = 12;
|
|
385
462
|
const maximumWorkspaceNoticeCharacters = 500;
|
|
386
463
|
|
|
@@ -543,6 +620,25 @@ const errorMessage = (errorArg: unknown): string => {
|
|
|
543
620
|
return String(errorArg || 'The operation failed.');
|
|
544
621
|
};
|
|
545
622
|
|
|
623
|
+
const waitForBrowserRendererLifecycle = async (
|
|
624
|
+
operationArg: Promise<void>,
|
|
625
|
+
actionArg: string,
|
|
626
|
+
): Promise<void> => {
|
|
627
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
628
|
+
try {
|
|
629
|
+
await Promise.race([
|
|
630
|
+
operationArg,
|
|
631
|
+
new Promise<never>((_resolve, reject) => {
|
|
632
|
+
timeout = setTimeout(() => {
|
|
633
|
+
reject(new Error(`Browser renderer ${actionArg} timed out.`));
|
|
634
|
+
}, browserRendererLifecycleTimeoutMs);
|
|
635
|
+
}),
|
|
636
|
+
]);
|
|
637
|
+
} finally {
|
|
638
|
+
if (timeout) clearTimeout(timeout);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
546
642
|
const formatPercentage = (valueArg: number | null | undefined): string => (
|
|
547
643
|
typeof valueArg === 'number' && Number.isFinite(valueArg)
|
|
548
644
|
? `${Math.round(valueArg)}%`
|
|
@@ -628,6 +724,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
628
724
|
private highCpuStartedAt: number | undefined;
|
|
629
725
|
private lastCpuSampleAt: number | undefined;
|
|
630
726
|
private refreshPending = false;
|
|
727
|
+
private refreshRelatedPending = false;
|
|
631
728
|
private projectsRequestId = 0;
|
|
632
729
|
private modelCatalogRequestId = 0;
|
|
633
730
|
private layoutRequestId = 0;
|
|
@@ -637,14 +734,27 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
637
734
|
private browserNavigationRequestId = 0;
|
|
638
735
|
private browserViewOpenAbortController: AbortController | undefined;
|
|
639
736
|
private browserNavigationAbortController: AbortController | undefined;
|
|
737
|
+
private browserViewRecoveryAbortController: AbortController | undefined;
|
|
640
738
|
private browserViewId = '';
|
|
641
739
|
private browserViewConnectionGeneration = -1;
|
|
642
|
-
private
|
|
740
|
+
private browserViewStreamGeneration = -1;
|
|
741
|
+
private browserViewActivated = false;
|
|
742
|
+
private browserViewPendingClose: {
|
|
743
|
+
viewId: string;
|
|
744
|
+
streamGeneration: number;
|
|
745
|
+
connectionGeneration: number;
|
|
746
|
+
attempts: number;
|
|
747
|
+
} | undefined;
|
|
748
|
+
private browserViewCloseTask: Promise<void> | undefined;
|
|
749
|
+
private browserViewCloseRetryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
643
750
|
private browserViewResourceId = '';
|
|
644
751
|
private browserViewAttachmentRevision = -1;
|
|
645
|
-
private browserViewState: interfaces.IControllerBrowserViewState | undefined;
|
|
646
752
|
private readonly browserViewListeners = new Set<plugins.TLiveBrowserEventListener>();
|
|
753
|
+
private browserViewTransport?: ControllerBrowserViewTransportClient;
|
|
754
|
+
private browserViewTransportRecoveryTask?: Promise<void>;
|
|
647
755
|
private browserRenderer?: plugins.LiveBrowserCanvasRenderer;
|
|
756
|
+
private browserAddressDraft = '';
|
|
757
|
+
private browserAddressEditing = false;
|
|
648
758
|
private suggestRequestId = 0;
|
|
649
759
|
private suggestDebounce: ReturnType<typeof setTimeout> | undefined;
|
|
650
760
|
private controllerStatus: interfaces.IControllerStatus | undefined;
|
|
@@ -757,13 +867,30 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
757
867
|
private readonly liveToolOverlays = new Map<string, ILiveToolOverlay>();
|
|
758
868
|
private readonly liveReasoningUpdates = new Map<string, interfaces.IControllerReasoningUpdate>();
|
|
759
869
|
private readonly liveTextUpdates = new Map<string, interfaces.IControllerTextUpdate>();
|
|
870
|
+
private readonly liveReasoningBaselines = new Map<
|
|
871
|
+
string,
|
|
872
|
+
ILiveMessageBaseline<interfaces.IControllerReasoningUpdate>
|
|
873
|
+
>();
|
|
874
|
+
private readonly liveTextBaselines = new Map<
|
|
875
|
+
string,
|
|
876
|
+
ILiveMessageBaseline<interfaces.IControllerTextUpdate>
|
|
877
|
+
>();
|
|
878
|
+
private readonly liveReasoningTextUtf8Bytes = new Map<string, number>();
|
|
879
|
+
private readonly liveTextTextUtf8Bytes = new Map<string, number>();
|
|
880
|
+
private readonly liveReasoningStoredBytes = new Map<string, number>();
|
|
881
|
+
private readonly liveTextStoredBytes = new Map<string, number>();
|
|
760
882
|
private readonly liveToolHydrationFloors = new Map<string, ILiveToolHydrationFloor>();
|
|
761
883
|
private readonly liveMessageHydrationFloors = new Map<string, ILiveMessageHydrationFloor>();
|
|
884
|
+
private readonly liveMessageDeltaBlocks = new Map<string, ILiveMessageDeltaBlock>();
|
|
762
885
|
private readonly liveHistoryBarriers = new Map<string, number>();
|
|
763
886
|
private liveToolOverlayBytes = 0;
|
|
764
887
|
private liveMessageOverlayBytes = 0;
|
|
765
888
|
private readonly liveToolStreamEpochs = new Map<TBrowserSessionHarnessId, number>();
|
|
766
889
|
private readonly liveMessageStreamEpochs = new Map<TBrowserSessionHarnessId, number>();
|
|
890
|
+
private canonicalChatProjection: ICanonicalChatProjection | undefined;
|
|
891
|
+
private canonicalChatProjectionDirty = false;
|
|
892
|
+
private canonicalTimelineRefreshPending = false;
|
|
893
|
+
private readonly canonicalMessageRefreshIds = new Set<string>();
|
|
767
894
|
private readonly subtaskPreviewEntries = new Map<string, ISubtaskPreviewEntry>();
|
|
768
895
|
private subtaskPreviewGeneration = 0;
|
|
769
896
|
private subtaskPreviewHydrationQueue: string[] = [];
|
|
@@ -841,8 +968,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
841
968
|
@plugins.deesElement.state()
|
|
842
969
|
accessor staleBundleVersion = '';
|
|
843
970
|
|
|
844
|
-
|
|
845
|
-
accessor systemMetrics: interfaces.IControllerSystemMetrics | undefined;
|
|
971
|
+
public systemMetrics: interfaces.IControllerSystemMetrics | undefined;
|
|
846
972
|
|
|
847
973
|
@plugins.deesElement.state()
|
|
848
974
|
accessor highCpuWarning = false;
|
|
@@ -919,6 +1045,9 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
919
1045
|
@plugins.deesElement.state()
|
|
920
1046
|
accessor browserViewLoading = false;
|
|
921
1047
|
|
|
1048
|
+
@plugins.deesElement.state()
|
|
1049
|
+
accessor browserViewState: interfaces.IControllerBrowserViewState | undefined;
|
|
1050
|
+
|
|
922
1051
|
@plugins.deesElement.state()
|
|
923
1052
|
accessor selectedTerminalId: TBrowserTerminalId | undefined;
|
|
924
1053
|
|
|
@@ -940,7 +1069,6 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
940
1069
|
onStatus: (statusArg) => this.handleConnectionStatus(statusArg),
|
|
941
1070
|
onControllerEvent: (eventArg) => this.handleControllerEvent(eventArg),
|
|
942
1071
|
onTerminalOutput: (outputArg) => this.handleTerminalOutput(outputArg),
|
|
943
|
-
onBrowserViewEvent: (eventArg) => this.handleBrowserViewEvent(eventArg),
|
|
944
1072
|
onTransportClose: (closeCodeArg, reasonArg) => {
|
|
945
1073
|
this.recordTransportClose(closeCodeArg, reasonArg);
|
|
946
1074
|
console.error(`Controller transport closed (code ${closeCodeArg}${reasonArg ? `: ${reasonArg}` : ''}).`);
|
|
@@ -949,6 +1077,13 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
949
1077
|
? 'The connection was closed because a response exceeded the transport frame limit. Reconnecting…'
|
|
950
1078
|
: `The connection was closed by the transport (code ${closeCodeArg}${reasonArg ? `: ${reasonArg}` : ''}). Reconnecting…`;
|
|
951
1079
|
},
|
|
1080
|
+
onReconnectExhausted: (attemptsArg, endpointArg) => {
|
|
1081
|
+
if (!this.componentConnected) return;
|
|
1082
|
+
this.connectionError = `Automatic reconnection stopped after ${attemptsArg} attempts to ${endpointArg}.`;
|
|
1083
|
+
void this.socketClient.stop().finally(() => {
|
|
1084
|
+
if (this.componentConnected) this.connectionStatus = 'disconnected';
|
|
1085
|
+
});
|
|
1086
|
+
},
|
|
952
1087
|
});
|
|
953
1088
|
this.sessionDraftSync = new SessionDraftSync({
|
|
954
1089
|
transport: {
|
|
@@ -1824,8 +1959,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
1824
1959
|
padding: 0 var(--dees-spacing-sm);
|
|
1825
1960
|
}
|
|
1826
1961
|
|
|
1827
|
-
.brandSubtitle
|
|
1828
|
-
.headerActions .refreshAction {
|
|
1962
|
+
.brandSubtitle {
|
|
1829
1963
|
display: none;
|
|
1830
1964
|
}
|
|
1831
1965
|
|
|
@@ -1979,6 +2113,8 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
1979
2113
|
|
|
1980
2114
|
this.connectionStatus = statusArg;
|
|
1981
2115
|
this.connectionGeneration += 1;
|
|
2116
|
+
if (this.browserViewCloseRetryTimer) clearTimeout(this.browserViewCloseRetryTimer);
|
|
2117
|
+
this.browserViewCloseRetryTimer = undefined;
|
|
1982
2118
|
if (this.browserViewPendingClose?.connectionGeneration !== this.connectionGeneration) {
|
|
1983
2119
|
this.browserViewPendingClose = undefined;
|
|
1984
2120
|
}
|
|
@@ -1997,6 +2133,8 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
1997
2133
|
|
|
1998
2134
|
private invalidateConnectionState(): void {
|
|
1999
2135
|
this.connectionGeneration += 1;
|
|
2136
|
+
if (this.browserViewCloseRetryTimer) clearTimeout(this.browserViewCloseRetryTimer);
|
|
2137
|
+
this.browserViewCloseRetryTimer = undefined;
|
|
2000
2138
|
if (this.browserViewPendingClose?.connectionGeneration !== this.connectionGeneration) {
|
|
2001
2139
|
this.browserViewPendingClose = undefined;
|
|
2002
2140
|
}
|
|
@@ -2016,6 +2154,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2016
2154
|
this.draftSessionDetailReadyId = undefined;
|
|
2017
2155
|
this.authenticated = false;
|
|
2018
2156
|
this.refreshPending = false;
|
|
2157
|
+
this.refreshRelatedPending = false;
|
|
2019
2158
|
this.detailRefreshPending = false;
|
|
2020
2159
|
this.projects = [];
|
|
2021
2160
|
this.creatingProject = false;
|
|
@@ -2513,12 +2652,54 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2513
2652
|
|
|
2514
2653
|
this.lastCpuSampleAt = sampledAt;
|
|
2515
2654
|
this.systemMetrics = metricsArg;
|
|
2655
|
+
this.updateSystemMetricsDom();
|
|
2516
2656
|
}
|
|
2517
2657
|
|
|
2518
2658
|
private resetHighCpuWarning(): void {
|
|
2519
2659
|
this.highCpuStartedAt = undefined;
|
|
2520
2660
|
this.lastCpuSampleAt = undefined;
|
|
2521
2661
|
this.highCpuWarning = false;
|
|
2662
|
+
this.updateSystemMetricsDom();
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
private updateSystemMetricsDom(): void {
|
|
2666
|
+
const metrics = this.systemMetrics;
|
|
2667
|
+
const memoryPercent = metrics?.memoryUsedBytes !== null
|
|
2668
|
+
&& metrics?.memoryUsedBytes !== undefined
|
|
2669
|
+
&& metrics.memoryTotalBytes !== null
|
|
2670
|
+
&& metrics.memoryTotalBytes !== undefined
|
|
2671
|
+
&& metrics.memoryTotalBytes > 0
|
|
2672
|
+
? (metrics.memoryUsedBytes / metrics.memoryTotalBytes) * 100
|
|
2673
|
+
: null;
|
|
2674
|
+
const cpu = this.shadowRoot?.querySelector<HTMLElement>('.cpuMetric');
|
|
2675
|
+
const memory = this.shadowRoot?.querySelector<HTMLElement>('.memoryMetric');
|
|
2676
|
+
const network = this.shadowRoot?.querySelector<HTMLElement>('.networkMetric');
|
|
2677
|
+
const cpuValue = cpu?.querySelector<HTMLElement>('.metricValue');
|
|
2678
|
+
const memoryValue = memory?.querySelector<HTMLElement>('.metricValue');
|
|
2679
|
+
const networkReceive = network?.querySelector<HTMLElement>('.networkReceive');
|
|
2680
|
+
const networkTransmit = network?.querySelector<HTMLElement>('.networkTransmit');
|
|
2681
|
+
if (cpu) {
|
|
2682
|
+
cpu.classList.toggle('highCpuWarning', this.highCpuWarning);
|
|
2683
|
+
cpu.title = this.highCpuWarning
|
|
2684
|
+
? 'Whole-system CPU usage has remained above 50% for at least 10 seconds'
|
|
2685
|
+
: 'Whole-system CPU usage';
|
|
2686
|
+
}
|
|
2687
|
+
if (cpuValue) cpuValue.textContent = formatPercentage(metrics?.cpuUsagePercent);
|
|
2688
|
+
if (memory) {
|
|
2689
|
+
memory.title = metrics?.memoryUsedBytes !== null
|
|
2690
|
+
&& metrics?.memoryUsedBytes !== undefined
|
|
2691
|
+
&& metrics.memoryTotalBytes !== null
|
|
2692
|
+
&& metrics.memoryTotalBytes !== undefined
|
|
2693
|
+
? `${formatBytes(metrics.memoryUsedBytes)} used of ${formatBytes(metrics.memoryTotalBytes)}`
|
|
2694
|
+
: 'Memory usage unavailable';
|
|
2695
|
+
}
|
|
2696
|
+
if (memoryValue) memoryValue.textContent = formatPercentage(memoryPercent);
|
|
2697
|
+
if (networkReceive) {
|
|
2698
|
+
networkReceive.textContent = `in ${formatByteRate(metrics?.networkReceiveBytesPerSecond)}`;
|
|
2699
|
+
}
|
|
2700
|
+
if (networkTransmit) {
|
|
2701
|
+
networkTransmit.textContent = `out ${formatByteRate(metrics?.networkTransmitBytesPerSecond)}`;
|
|
2702
|
+
}
|
|
2522
2703
|
}
|
|
2523
2704
|
|
|
2524
2705
|
private readonly showLegalDisclosure = (): void => {
|
|
@@ -2843,12 +3024,13 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2843
3024
|
}
|
|
2844
3025
|
}
|
|
2845
3026
|
|
|
2846
|
-
private refreshSessions(): Promise<void> {
|
|
3027
|
+
private refreshSessions(includeRelatedArg = true): Promise<void> {
|
|
2847
3028
|
if (!this.authenticated || !this.socketClient.isConnected) {
|
|
2848
3029
|
return Promise.resolve();
|
|
2849
3030
|
}
|
|
2850
3031
|
|
|
2851
3032
|
this.refreshPending = true;
|
|
3033
|
+
this.refreshRelatedPending ||= includeRelatedArg;
|
|
2852
3034
|
if (!this.refreshInFlight) {
|
|
2853
3035
|
this.refreshInFlight = this.runRefreshQueue();
|
|
2854
3036
|
}
|
|
@@ -2930,8 +3112,16 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2930
3112
|
if (harnessIdArg === undefined) {
|
|
2931
3113
|
this.liveReasoningUpdates.clear();
|
|
2932
3114
|
this.liveTextUpdates.clear();
|
|
3115
|
+
this.liveReasoningBaselines.clear();
|
|
3116
|
+
this.liveTextBaselines.clear();
|
|
3117
|
+
this.liveReasoningTextUtf8Bytes.clear();
|
|
3118
|
+
this.liveTextTextUtf8Bytes.clear();
|
|
3119
|
+
this.liveReasoningStoredBytes.clear();
|
|
3120
|
+
this.liveTextStoredBytes.clear();
|
|
2933
3121
|
this.liveMessageHydrationFloors.clear();
|
|
3122
|
+
this.liveMessageDeltaBlocks.clear();
|
|
2934
3123
|
this.liveMessageOverlayBytes = 0;
|
|
3124
|
+
this.canonicalChatProjectionDirty = true;
|
|
2935
3125
|
return;
|
|
2936
3126
|
}
|
|
2937
3127
|
for (const [key, floor] of this.liveMessageHydrationFloors) {
|
|
@@ -2943,6 +3133,18 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2943
3133
|
for (const [key, update] of this.liveTextUpdates) {
|
|
2944
3134
|
if (update.sessionId.harnessId === harnessIdArg) this.deleteLiveTextUpdate(key);
|
|
2945
3135
|
}
|
|
3136
|
+
for (const [key, baseline] of this.liveReasoningBaselines) {
|
|
3137
|
+
if (baseline.update.sessionId.harnessId === harnessIdArg) {
|
|
3138
|
+
this.deleteLiveReasoningBaseline(key);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
for (const [key, baseline] of this.liveTextBaselines) {
|
|
3142
|
+
if (baseline.update.sessionId.harnessId === harnessIdArg) this.deleteLiveTextBaseline(key);
|
|
3143
|
+
}
|
|
3144
|
+
for (const [key, block] of this.liveMessageDeltaBlocks) {
|
|
3145
|
+
if (block.sessionId.harnessId === harnessIdArg) this.liveMessageDeltaBlocks.delete(key);
|
|
3146
|
+
}
|
|
3147
|
+
this.canonicalChatProjectionDirty = true;
|
|
2946
3148
|
}
|
|
2947
3149
|
|
|
2948
3150
|
private clearLiveToolSession(
|
|
@@ -2967,21 +3169,38 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2967
3169
|
&& controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
2968
3170
|
) this.deleteLiveTextUpdate(key);
|
|
2969
3171
|
}
|
|
3172
|
+
for (const [key, baseline] of this.liveReasoningBaselines) {
|
|
3173
|
+
if (
|
|
3174
|
+
key.startsWith(`${projectIdArg}\0`)
|
|
3175
|
+
&& controllerRuntimeIdsEqual(baseline.update.sessionId, sessionIdArg)
|
|
3176
|
+
) this.deleteLiveReasoningBaseline(key);
|
|
3177
|
+
}
|
|
3178
|
+
for (const [key, baseline] of this.liveTextBaselines) {
|
|
3179
|
+
if (
|
|
3180
|
+
key.startsWith(`${projectIdArg}\0`)
|
|
3181
|
+
&& controllerRuntimeIdsEqual(baseline.update.sessionId, sessionIdArg)
|
|
3182
|
+
) this.deleteLiveTextBaseline(key);
|
|
3183
|
+
}
|
|
3184
|
+
this.canonicalChatProjectionDirty = true;
|
|
2970
3185
|
// Keep both hydration floors: they continue fencing snapshots already
|
|
2971
3186
|
// represented by the last authoritative detail until its replacement lands.
|
|
2972
3187
|
}
|
|
2973
3188
|
|
|
2974
3189
|
private liveMessageUpdateBytes(updateArg: unknown): number {
|
|
2975
|
-
return
|
|
3190
|
+
return browserTextEncoder.encode(JSON.stringify(updateArg)).byteLength;
|
|
2976
3191
|
}
|
|
2977
3192
|
|
|
2978
3193
|
private deleteLiveReasoningUpdate(keyArg: string): void {
|
|
2979
3194
|
const update = this.liveReasoningUpdates.get(keyArg);
|
|
2980
3195
|
if (!update) return;
|
|
2981
3196
|
this.liveReasoningUpdates.delete(keyArg);
|
|
3197
|
+
this.liveReasoningTextUtf8Bytes.delete(keyArg);
|
|
3198
|
+
const bytes = this.liveReasoningStoredBytes.get(keyArg)
|
|
3199
|
+
?? this.liveMessageUpdateBytes(update);
|
|
3200
|
+
this.liveReasoningStoredBytes.delete(keyArg);
|
|
2982
3201
|
this.liveMessageOverlayBytes = Math.max(
|
|
2983
3202
|
0,
|
|
2984
|
-
this.liveMessageOverlayBytes -
|
|
3203
|
+
this.liveMessageOverlayBytes - bytes,
|
|
2985
3204
|
);
|
|
2986
3205
|
}
|
|
2987
3206
|
|
|
@@ -2989,29 +3208,61 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2989
3208
|
const update = this.liveTextUpdates.get(keyArg);
|
|
2990
3209
|
if (!update) return;
|
|
2991
3210
|
this.liveTextUpdates.delete(keyArg);
|
|
3211
|
+
this.liveTextTextUtf8Bytes.delete(keyArg);
|
|
3212
|
+
const bytes = this.liveTextStoredBytes.get(keyArg)
|
|
3213
|
+
?? this.liveMessageUpdateBytes(update);
|
|
3214
|
+
this.liveTextStoredBytes.delete(keyArg);
|
|
2992
3215
|
this.liveMessageOverlayBytes = Math.max(
|
|
2993
3216
|
0,
|
|
2994
|
-
this.liveMessageOverlayBytes -
|
|
3217
|
+
this.liveMessageOverlayBytes - bytes,
|
|
2995
3218
|
);
|
|
2996
3219
|
}
|
|
2997
3220
|
|
|
3221
|
+
private deleteLiveReasoningBaseline(keyArg: string): void {
|
|
3222
|
+
const baseline = this.liveReasoningBaselines.get(keyArg);
|
|
3223
|
+
if (!baseline) return;
|
|
3224
|
+
this.liveReasoningBaselines.delete(keyArg);
|
|
3225
|
+
this.liveMessageOverlayBytes = Math.max(0, this.liveMessageOverlayBytes - baseline.bytes);
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
private deleteLiveTextBaseline(keyArg: string): void {
|
|
3229
|
+
const baseline = this.liveTextBaselines.get(keyArg);
|
|
3230
|
+
if (!baseline) return;
|
|
3231
|
+
this.liveTextBaselines.delete(keyArg);
|
|
3232
|
+
this.liveMessageOverlayBytes = Math.max(0, this.liveMessageOverlayBytes - baseline.bytes);
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
private liveMessageEntryCount(): number {
|
|
3236
|
+
return this.liveReasoningUpdates.size
|
|
3237
|
+
+ this.liveTextUpdates.size
|
|
3238
|
+
+ this.liveReasoningBaselines.size
|
|
3239
|
+
+ this.liveTextBaselines.size;
|
|
3240
|
+
}
|
|
3241
|
+
|
|
2998
3242
|
private storeLiveReasoningUpdate(
|
|
2999
3243
|
keyArg: string,
|
|
3000
3244
|
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3001
3245
|
): boolean {
|
|
3002
3246
|
const bytes = this.liveMessageUpdateBytes(updateArg);
|
|
3003
3247
|
const existing = this.liveReasoningUpdates.get(keyArg);
|
|
3004
|
-
const
|
|
3248
|
+
const baseline = this.liveReasoningBaselines.get(keyArg);
|
|
3249
|
+
const existingBytes = existing
|
|
3250
|
+
? this.liveReasoningStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(existing)
|
|
3251
|
+
: baseline?.bytes ?? 0;
|
|
3005
3252
|
if (
|
|
3006
|
-
(!existing &&
|
|
3253
|
+
(!existing && !baseline && this.liveMessageEntryCount() >= maxLiveToolOverlayEntries)
|
|
3007
3254
|
|| this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes
|
|
3008
3255
|
) {
|
|
3009
|
-
this.clearLiveMessageState();
|
|
3010
|
-
this.refreshSessionsNow();
|
|
3011
3256
|
return false;
|
|
3012
3257
|
}
|
|
3013
3258
|
if (existing) this.deleteLiveReasoningUpdate(keyArg);
|
|
3259
|
+
if (baseline) this.deleteLiveReasoningBaseline(keyArg);
|
|
3014
3260
|
this.liveReasoningUpdates.set(keyArg, updateArg);
|
|
3261
|
+
this.liveReasoningTextUtf8Bytes.set(
|
|
3262
|
+
keyArg,
|
|
3263
|
+
browserTextEncoder.encode(updateArg.text).byteLength,
|
|
3264
|
+
);
|
|
3265
|
+
this.liveReasoningStoredBytes.set(keyArg, bytes);
|
|
3015
3266
|
this.liveMessageOverlayBytes += bytes;
|
|
3016
3267
|
return true;
|
|
3017
3268
|
}
|
|
@@ -3022,21 +3273,168 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3022
3273
|
): boolean {
|
|
3023
3274
|
const bytes = this.liveMessageUpdateBytes(updateArg);
|
|
3024
3275
|
const existing = this.liveTextUpdates.get(keyArg);
|
|
3025
|
-
const
|
|
3276
|
+
const baseline = this.liveTextBaselines.get(keyArg);
|
|
3277
|
+
const existingBytes = existing
|
|
3278
|
+
? this.liveTextStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(existing)
|
|
3279
|
+
: baseline?.bytes ?? 0;
|
|
3026
3280
|
if (
|
|
3027
|
-
(!existing &&
|
|
3281
|
+
(!existing && !baseline && this.liveMessageEntryCount() >= maxLiveToolOverlayEntries)
|
|
3028
3282
|
|| this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes
|
|
3029
3283
|
) {
|
|
3030
|
-
this.clearLiveMessageState();
|
|
3031
|
-
this.refreshSessionsNow();
|
|
3032
3284
|
return false;
|
|
3033
3285
|
}
|
|
3034
3286
|
if (existing) this.deleteLiveTextUpdate(keyArg);
|
|
3287
|
+
if (baseline) this.deleteLiveTextBaseline(keyArg);
|
|
3288
|
+
this.liveTextUpdates.set(keyArg, updateArg);
|
|
3289
|
+
this.liveTextTextUtf8Bytes.set(keyArg, browserTextEncoder.encode(updateArg.text).byteLength);
|
|
3290
|
+
this.liveTextStoredBytes.set(keyArg, bytes);
|
|
3291
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3292
|
+
return true;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
private retireLiveReasoningUpdateToBaseline(
|
|
3296
|
+
keyArg: string,
|
|
3297
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3298
|
+
textArg: string,
|
|
3299
|
+
cursorArg: interfaces.IControllerMessageStreamCursor,
|
|
3300
|
+
): void {
|
|
3301
|
+
const update = { ...updateArg, text: textArg, revision: cursorArg.revision };
|
|
3302
|
+
const bytes = this.liveMessageUpdateBytes(update);
|
|
3303
|
+
const existingBytes = this.liveReasoningStoredBytes.get(keyArg)
|
|
3304
|
+
?? this.liveReasoningBaselines.get(keyArg)?.bytes
|
|
3305
|
+
?? this.liveMessageUpdateBytes(updateArg);
|
|
3306
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) {
|
|
3307
|
+
this.deleteLiveReasoningUpdate(keyArg);
|
|
3308
|
+
this.deleteLiveReasoningBaseline(keyArg);
|
|
3309
|
+
return;
|
|
3310
|
+
}
|
|
3311
|
+
this.deleteLiveReasoningUpdate(keyArg);
|
|
3312
|
+
this.deleteLiveReasoningBaseline(keyArg);
|
|
3313
|
+
this.liveReasoningBaselines.set(keyArg, {
|
|
3314
|
+
update,
|
|
3315
|
+
textUtf8Bytes: browserTextEncoder.encode(textArg).byteLength,
|
|
3316
|
+
bytes,
|
|
3317
|
+
});
|
|
3318
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
private retireLiveTextUpdateToBaseline(
|
|
3322
|
+
keyArg: string,
|
|
3323
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
3324
|
+
textArg: string,
|
|
3325
|
+
cursorArg: interfaces.IControllerMessageStreamCursor,
|
|
3326
|
+
): void {
|
|
3327
|
+
const update = { ...updateArg, text: textArg, revision: cursorArg.revision };
|
|
3328
|
+
const bytes = this.liveMessageUpdateBytes(update);
|
|
3329
|
+
const existingBytes = this.liveTextStoredBytes.get(keyArg)
|
|
3330
|
+
?? this.liveTextBaselines.get(keyArg)?.bytes
|
|
3331
|
+
?? this.liveMessageUpdateBytes(updateArg);
|
|
3332
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) {
|
|
3333
|
+
this.deleteLiveTextUpdate(keyArg);
|
|
3334
|
+
this.deleteLiveTextBaseline(keyArg);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
this.deleteLiveTextUpdate(keyArg);
|
|
3338
|
+
this.deleteLiveTextBaseline(keyArg);
|
|
3339
|
+
this.liveTextBaselines.set(keyArg, {
|
|
3340
|
+
update,
|
|
3341
|
+
textUtf8Bytes: browserTextEncoder.encode(textArg).byteLength,
|
|
3342
|
+
bytes,
|
|
3343
|
+
});
|
|
3344
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
private storeLiveReasoningDeltaState(
|
|
3348
|
+
keyArg: string,
|
|
3349
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3350
|
+
textUtf8BytesArg: number,
|
|
3351
|
+
deltaArg: string,
|
|
3352
|
+
): boolean {
|
|
3353
|
+
const active = this.liveReasoningUpdates.get(keyArg);
|
|
3354
|
+
const baseline = this.liveReasoningBaselines.get(keyArg);
|
|
3355
|
+
const existingBytes = active
|
|
3356
|
+
? this.liveReasoningStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(active)
|
|
3357
|
+
: baseline?.bytes ?? 0;
|
|
3358
|
+
const bytes = existingBytes + Math.max(
|
|
3359
|
+
browserTextEncoder.encode(deltaArg).byteLength,
|
|
3360
|
+
deltaArg.length * 6,
|
|
3361
|
+
);
|
|
3362
|
+
if (!active && !baseline) return false;
|
|
3363
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) return false;
|
|
3364
|
+
this.deleteLiveReasoningUpdate(keyArg);
|
|
3365
|
+
this.deleteLiveReasoningBaseline(keyArg);
|
|
3366
|
+
this.liveReasoningUpdates.set(keyArg, updateArg);
|
|
3367
|
+
this.liveReasoningTextUtf8Bytes.set(keyArg, textUtf8BytesArg);
|
|
3368
|
+
this.liveReasoningStoredBytes.set(keyArg, bytes);
|
|
3369
|
+
this.liveMessageOverlayBytes += bytes;
|
|
3370
|
+
return true;
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
private storeLiveTextDeltaState(
|
|
3374
|
+
keyArg: string,
|
|
3375
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
3376
|
+
textUtf8BytesArg: number,
|
|
3377
|
+
deltaArg: string,
|
|
3378
|
+
): boolean {
|
|
3379
|
+
const active = this.liveTextUpdates.get(keyArg);
|
|
3380
|
+
const baseline = this.liveTextBaselines.get(keyArg);
|
|
3381
|
+
const existingBytes = active
|
|
3382
|
+
? this.liveTextStoredBytes.get(keyArg) ?? this.liveMessageUpdateBytes(active)
|
|
3383
|
+
: baseline?.bytes ?? 0;
|
|
3384
|
+
const bytes = existingBytes + Math.max(
|
|
3385
|
+
browserTextEncoder.encode(deltaArg).byteLength,
|
|
3386
|
+
deltaArg.length * 6,
|
|
3387
|
+
);
|
|
3388
|
+
if (!active && !baseline) return false;
|
|
3389
|
+
if (this.liveMessageOverlayBytes - existingBytes + bytes > maxLiveToolOverlayBytes) return false;
|
|
3390
|
+
this.deleteLiveTextUpdate(keyArg);
|
|
3391
|
+
this.deleteLiveTextBaseline(keyArg);
|
|
3035
3392
|
this.liveTextUpdates.set(keyArg, updateArg);
|
|
3393
|
+
this.liveTextTextUtf8Bytes.set(keyArg, textUtf8BytesArg);
|
|
3394
|
+
this.liveTextStoredBytes.set(keyArg, bytes);
|
|
3036
3395
|
this.liveMessageOverlayBytes += bytes;
|
|
3037
3396
|
return true;
|
|
3038
3397
|
}
|
|
3039
3398
|
|
|
3399
|
+
private liveMessageUpdateKey(
|
|
3400
|
+
projectIdArg: string,
|
|
3401
|
+
updateArg: interfaces.IControllerReasoningUpdate
|
|
3402
|
+
| interfaces.IControllerTextUpdate
|
|
3403
|
+
| interfaces.IControllerReasoningDelta
|
|
3404
|
+
| interfaces.IControllerTextDelta,
|
|
3405
|
+
): string {
|
|
3406
|
+
return `${projectIdArg}\0${controllerRuntimeIdToUiKey(updateArg.sessionId)}\0${controllerRuntimeIdToUiKey(updateArg.partId)}`;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
private blockLiveMessageDeltas(
|
|
3410
|
+
projectIdArg: string,
|
|
3411
|
+
sessionIdArg: TBrowserSessionId,
|
|
3412
|
+
streamEpochArg: number,
|
|
3413
|
+
refreshArg = true,
|
|
3414
|
+
): void {
|
|
3415
|
+
const sessionKey = this.sessionOperationKey(projectIdArg, sessionIdArg);
|
|
3416
|
+
if (this.liveMessageDeltaBlocks.get(sessionKey)?.streamEpoch === streamEpochArg) return;
|
|
3417
|
+
this.liveMessageDeltaBlocks.set(sessionKey, {
|
|
3418
|
+
sessionId: sessionIdArg,
|
|
3419
|
+
streamEpoch: streamEpochArg,
|
|
3420
|
+
});
|
|
3421
|
+
if (
|
|
3422
|
+
refreshArg
|
|
3423
|
+
&& projectIdArg === this.selectedProjectId
|
|
3424
|
+
&& controllerRuntimeIdsEqual(sessionIdArg, this.selectedSessionId)
|
|
3425
|
+
) void this.refreshSelectedSessionDetail();
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
private liveMessageDeltasBlocked(
|
|
3429
|
+
projectIdArg: string,
|
|
3430
|
+
sessionIdArg: TBrowserSessionId,
|
|
3431
|
+
streamEpochArg: number,
|
|
3432
|
+
): boolean {
|
|
3433
|
+
return this.liveMessageDeltaBlocks.get(
|
|
3434
|
+
this.sessionOperationKey(projectIdArg, sessionIdArg),
|
|
3435
|
+
)?.streamEpoch === streamEpochArg;
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3040
3438
|
private acceptLiveToolEpoch(
|
|
3041
3439
|
harnessIdArg: TBrowserSessionHarnessId,
|
|
3042
3440
|
epochArg: number,
|
|
@@ -3067,6 +3465,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3067
3465
|
harnessIdArg: TBrowserSessionHarnessId,
|
|
3068
3466
|
epochArg: number,
|
|
3069
3467
|
handleBarrierArg = true,
|
|
3468
|
+
refreshSessionListArg = true,
|
|
3070
3469
|
): boolean {
|
|
3071
3470
|
const currentEpoch = this.liveMessageStreamEpochs.get(harnessIdArg) ?? 0;
|
|
3072
3471
|
if (epochArg < currentEpoch) return false;
|
|
@@ -3083,7 +3482,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3083
3482
|
this.invalidateDetailEnrichment();
|
|
3084
3483
|
void this.refreshSelectedSessionDetail();
|
|
3085
3484
|
}
|
|
3086
|
-
this.refreshSessionsNow();
|
|
3485
|
+
if (refreshSessionListArg) this.refreshSessionsNow();
|
|
3087
3486
|
}
|
|
3088
3487
|
}
|
|
3089
3488
|
return true;
|
|
@@ -3153,6 +3552,20 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3153
3552
|
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3154
3553
|
detailArg: IControllerSessionRenderDetail,
|
|
3155
3554
|
): boolean {
|
|
3555
|
+
const target = this.authoritativeReasoningTarget(updateArg, detailArg);
|
|
3556
|
+
return target !== undefined
|
|
3557
|
+
&& (target.part.text === updateArg.text || target.part.text.startsWith(updateArg.text))
|
|
3558
|
+
&& (
|
|
3559
|
+
updateArg.status === 'running'
|
|
3560
|
+
|| target.part.endedAt !== undefined
|
|
3561
|
+
|| target.message.streaming !== true
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
private authoritativeReasoningTarget(
|
|
3566
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
3567
|
+
detailArg: IControllerSessionRenderDetail,
|
|
3568
|
+
): { message: interfaces.IControllerMessage; part: interfaces.IControllerReasoningPart } | undefined {
|
|
3156
3569
|
const message = detailArg.messages.find((messageArg) => (
|
|
3157
3570
|
messageArg.role === 'assistant'
|
|
3158
3571
|
&& (
|
|
@@ -3169,16 +3582,24 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3169
3582
|
const part = message?.reasoning?.find((partArg) => (
|
|
3170
3583
|
controllerRuntimeIdsEqual(partArg.id, updateArg.partId)
|
|
3171
3584
|
));
|
|
3172
|
-
return part
|
|
3173
|
-
&& (part.text === updateArg.text || part.text.startsWith(updateArg.text))
|
|
3174
|
-
&& (updateArg.status === 'running' || part.endedAt !== undefined || message?.streaming !== true);
|
|
3585
|
+
return message && part ? { message, part } : undefined;
|
|
3175
3586
|
}
|
|
3176
3587
|
|
|
3177
3588
|
private authoritativeTextUpdateCovers(
|
|
3178
3589
|
updateArg: interfaces.IControllerTextUpdate,
|
|
3179
3590
|
detailArg: IControllerSessionRenderDetail,
|
|
3180
3591
|
): boolean {
|
|
3181
|
-
const message =
|
|
3592
|
+
const message = this.authoritativeTextTarget(updateArg, detailArg);
|
|
3593
|
+
return message !== undefined
|
|
3594
|
+
&& (message.text === updateArg.text || message.text.startsWith(updateArg.text))
|
|
3595
|
+
&& (updateArg.status === 'running' || message.streaming !== true);
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
private authoritativeTextTarget(
|
|
3599
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
3600
|
+
detailArg: IControllerSessionRenderDetail,
|
|
3601
|
+
): interfaces.IControllerMessage | undefined {
|
|
3602
|
+
return detailArg.messages.find((messageArg) => (
|
|
3182
3603
|
messageArg.role === 'assistant'
|
|
3183
3604
|
&& (
|
|
3184
3605
|
controllerRuntimeIdsEqual(messageArg.id, updateArg.partId)
|
|
@@ -3189,9 +3610,6 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3189
3610
|
)
|
|
3190
3611
|
)
|
|
3191
3612
|
));
|
|
3192
|
-
return message !== undefined
|
|
3193
|
-
&& (message.text === updateArg.text || message.text.startsWith(updateArg.text))
|
|
3194
|
-
&& (updateArg.status === 'running' || message.streaming !== true);
|
|
3195
3613
|
}
|
|
3196
3614
|
|
|
3197
3615
|
private liveToolSnapshotIsNewer(
|
|
@@ -3259,6 +3677,257 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3259
3677
|
return this.authoritativeToolCallCovers(merged, toolCallArg) ? undefined : merged;
|
|
3260
3678
|
}
|
|
3261
3679
|
|
|
3680
|
+
private ensureCanonicalChatProjection(
|
|
3681
|
+
detailArg: IControllerSessionRenderDetail,
|
|
3682
|
+
projectIdArg: string,
|
|
3683
|
+
sessionIdArg: TBrowserSessionId,
|
|
3684
|
+
): ICanonicalChatProjection {
|
|
3685
|
+
const key = this.sessionOperationKey(projectIdArg, sessionIdArg);
|
|
3686
|
+
let projection = this.canonicalChatProjection;
|
|
3687
|
+
if (!projection || projection.key !== key) {
|
|
3688
|
+
projection = {
|
|
3689
|
+
key,
|
|
3690
|
+
sourceDetail: undefined,
|
|
3691
|
+
messages: [],
|
|
3692
|
+
messagesById: new Map(),
|
|
3693
|
+
messagesByOrder: new Map(),
|
|
3694
|
+
reasoningById: new Map(),
|
|
3695
|
+
};
|
|
3696
|
+
this.canonicalChatProjection = projection;
|
|
3697
|
+
this.canonicalChatProjectionDirty = true;
|
|
3698
|
+
}
|
|
3699
|
+
if (projection.sourceDetail === detailArg && !this.canonicalChatProjectionDirty) {
|
|
3700
|
+
return projection;
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
const messages = detailArg.messages
|
|
3704
|
+
.filter((messageArg) => messageArg.toolCall?.name !== 'question')
|
|
3705
|
+
.map((messageArg) => this.toHarnessMessage(messageArg, projectIdArg, sessionIdArg));
|
|
3706
|
+
const visibleMessageIds = new Set(messages.map((message) => message.id));
|
|
3707
|
+
for (const prompt of detailArg.pendingPrompts) {
|
|
3708
|
+
const id = controllerRuntimeIdToUiKey(prompt.id);
|
|
3709
|
+
if (visibleMessageIds.has(id)) continue;
|
|
3710
|
+
visibleMessageIds.add(id);
|
|
3711
|
+
messages.push({
|
|
3712
|
+
id,
|
|
3713
|
+
role: 'user',
|
|
3714
|
+
text: prompt.text,
|
|
3715
|
+
createdAt: prompt.createdAt,
|
|
3716
|
+
markdown: false,
|
|
3717
|
+
attachments: prompt.attachments.map((attachment) => ({ ...attachment })),
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
this.mergeLiveReasoningMessages(messages, projectIdArg, sessionIdArg);
|
|
3721
|
+
this.mergeLiveTextMessages(messages, projectIdArg, sessionIdArg);
|
|
3722
|
+
this.mergeLiveToolMessages(messages, projectIdArg, sessionIdArg);
|
|
3723
|
+
const structureChanged = this.canonicalHarnessStructureChanged(
|
|
3724
|
+
projection.messages,
|
|
3725
|
+
messages,
|
|
3726
|
+
);
|
|
3727
|
+
this.reconcileCanonicalHarnessMessages(projection, messages);
|
|
3728
|
+
this.canonicalTimelineRefreshPending ||= structureChanged;
|
|
3729
|
+
projection.sourceDetail = detailArg;
|
|
3730
|
+
this.canonicalChatProjectionDirty = false;
|
|
3731
|
+
return projection;
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
private canonicalHarnessStructureChanged(
|
|
3735
|
+
currentArg: readonly plugins.deesCatalog.IHarnessMessage[],
|
|
3736
|
+
nextArg: readonly plugins.deesCatalog.IHarnessMessage[],
|
|
3737
|
+
): boolean {
|
|
3738
|
+
return currentArg.length !== nextArg.length || currentArg.some((current, index) => {
|
|
3739
|
+
const next = nextArg[index];
|
|
3740
|
+
return !next
|
|
3741
|
+
|| current.id !== next.id
|
|
3742
|
+
|| current.role !== next.role
|
|
3743
|
+
|| current.toolCall?.id !== next.toolCall?.id
|
|
3744
|
+
|| current.toolCall?.name !== next.toolCall?.name
|
|
3745
|
+
|| !this.liveMessageOrdersEqual(current.order, next.order);
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
private reconcileCanonicalHarnessMessages(
|
|
3750
|
+
projectionArg: ICanonicalChatProjection,
|
|
3751
|
+
nextMessagesArg: plugins.deesCatalog.IHarnessMessage[],
|
|
3752
|
+
): void {
|
|
3753
|
+
const currentById = new Map(
|
|
3754
|
+
projectionArg.messages.map((message) => [message.id, message]),
|
|
3755
|
+
);
|
|
3756
|
+
const reconciled = nextMessagesArg.map((nextMessage) => {
|
|
3757
|
+
const current = currentById.get(nextMessage.id);
|
|
3758
|
+
if (!current) return nextMessage;
|
|
3759
|
+
if (!controllerJsonValuesEqual(current, nextMessage)) {
|
|
3760
|
+
this.canonicalMessageRefreshIds.add(current.id);
|
|
3761
|
+
}
|
|
3762
|
+
this.reconcileCanonicalHarnessMessage(current, nextMessage);
|
|
3763
|
+
return current;
|
|
3764
|
+
});
|
|
3765
|
+
projectionArg.messages.splice(0, projectionArg.messages.length, ...reconciled);
|
|
3766
|
+
this.rebuildCanonicalChatIndexes(projectionArg);
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
private reconcileCanonicalHarnessMessage(
|
|
3770
|
+
targetArg: plugins.deesCatalog.IHarnessMessage,
|
|
3771
|
+
sourceArg: plugins.deesCatalog.IHarnessMessage,
|
|
3772
|
+
): void {
|
|
3773
|
+
const targetReasoning = targetArg.reasoning;
|
|
3774
|
+
const sourceReasoning = sourceArg.reasoning;
|
|
3775
|
+
const targetToolCall = targetArg.toolCall;
|
|
3776
|
+
const sourceToolCall = sourceArg.toolCall;
|
|
3777
|
+
const target = targetArg as unknown as Record<string, unknown>;
|
|
3778
|
+
const source = sourceArg as unknown as Record<string, unknown>;
|
|
3779
|
+
for (const key of Object.keys(target)) {
|
|
3780
|
+
if (!(key in source)) delete target[key];
|
|
3781
|
+
}
|
|
3782
|
+
for (const [key, value] of Object.entries(source)) {
|
|
3783
|
+
if (key !== 'reasoning' && key !== 'toolCall') target[key] = value;
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
if (sourceReasoning) {
|
|
3787
|
+
const currentById = new Map((targetReasoning ?? []).map((part) => [part.id, part]));
|
|
3788
|
+
const reasoning = sourceReasoning.map((sourcePart) => {
|
|
3789
|
+
const currentPart = currentById.get(sourcePart.id);
|
|
3790
|
+
if (!currentPart) return sourcePart;
|
|
3791
|
+
const current = currentPart as unknown as Record<string, unknown>;
|
|
3792
|
+
const next = sourcePart as unknown as Record<string, unknown>;
|
|
3793
|
+
for (const key of Object.keys(current)) {
|
|
3794
|
+
if (!(key in next)) delete current[key];
|
|
3795
|
+
}
|
|
3796
|
+
Object.assign(current, next);
|
|
3797
|
+
return currentPart;
|
|
3798
|
+
});
|
|
3799
|
+
if (targetReasoning) targetReasoning.splice(0, targetReasoning.length, ...reasoning);
|
|
3800
|
+
else targetArg.reasoning = reasoning;
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
if (sourceToolCall) {
|
|
3804
|
+
if (targetToolCall?.id === sourceToolCall.id) {
|
|
3805
|
+
const current = targetToolCall as unknown as Record<string, unknown>;
|
|
3806
|
+
const next = sourceToolCall as unknown as Record<string, unknown>;
|
|
3807
|
+
for (const key of Object.keys(current)) {
|
|
3808
|
+
if (!(key in next)) delete current[key];
|
|
3809
|
+
}
|
|
3810
|
+
Object.assign(current, next);
|
|
3811
|
+
targetArg.toolCall = targetToolCall;
|
|
3812
|
+
} else {
|
|
3813
|
+
targetArg.toolCall = sourceToolCall;
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
private rebuildCanonicalChatIndexes(projectionArg: ICanonicalChatProjection): void {
|
|
3819
|
+
projectionArg.messagesById.clear();
|
|
3820
|
+
projectionArg.messagesByOrder.clear();
|
|
3821
|
+
projectionArg.reasoningById.clear();
|
|
3822
|
+
for (const message of projectionArg.messages) {
|
|
3823
|
+
projectionArg.messagesById.set(message.id, message);
|
|
3824
|
+
if (message.order && !projectionArg.messagesByOrder.has(transcriptOrderKey(message.order))) {
|
|
3825
|
+
projectionArg.messagesByOrder.set(transcriptOrderKey(message.order), message);
|
|
3826
|
+
}
|
|
3827
|
+
for (const part of message.reasoning ?? []) {
|
|
3828
|
+
projectionArg.reasoningById.set(part.id, { message, part });
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
private selectedCanonicalChatProjection(
|
|
3834
|
+
projectIdArg: string,
|
|
3835
|
+
sessionIdArg: TBrowserSessionId,
|
|
3836
|
+
): ICanonicalChatProjection | undefined {
|
|
3837
|
+
const detail = this.sessionDetail;
|
|
3838
|
+
if (
|
|
3839
|
+
projectIdArg !== this.selectedProjectId
|
|
3840
|
+
|| !detail
|
|
3841
|
+
|| !controllerRuntimeIdsEqual(detail.session.id, sessionIdArg)
|
|
3842
|
+
|| !controllerRuntimeIdsEqual(this.selectedSessionId, sessionIdArg)
|
|
3843
|
+
) return undefined;
|
|
3844
|
+
return this.ensureCanonicalChatProjection(detail, projectIdArg, sessionIdArg);
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
private canonicalTextTarget(
|
|
3848
|
+
projectionArg: ICanonicalChatProjection,
|
|
3849
|
+
updateArg: interfaces.IControllerTextUpdate | interfaces.IControllerTextDelta,
|
|
3850
|
+
): plugins.deesCatalog.IHarnessMessage | undefined {
|
|
3851
|
+
return projectionArg.messagesById.get(controllerRuntimeIdToUiKey(updateArg.messageId))
|
|
3852
|
+
?? projectionArg.messagesById.get(controllerRuntimeIdToUiKey(updateArg.partId))
|
|
3853
|
+
?? (updateArg.order
|
|
3854
|
+
? projectionArg.messagesByOrder.get(transcriptOrderKey(updateArg.order))
|
|
3855
|
+
: undefined);
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
private canonicalReasoningTarget(
|
|
3859
|
+
projectionArg: ICanonicalChatProjection,
|
|
3860
|
+
updateArg: interfaces.IControllerReasoningUpdate | interfaces.IControllerReasoningDelta,
|
|
3861
|
+
): ICanonicalReasoningTarget | undefined {
|
|
3862
|
+
const reasoningId = controllerRuntimeIdToUiKey(updateArg.partId);
|
|
3863
|
+
const partTarget = projectionArg.reasoningById.get(reasoningId);
|
|
3864
|
+
const message = projectionArg.messagesById.get(controllerRuntimeIdToUiKey(updateArg.messageId))
|
|
3865
|
+
?? partTarget?.message
|
|
3866
|
+
?? (updateArg.order
|
|
3867
|
+
? projectionArg.messagesByOrder.get(transcriptOrderKey(updateArg.order))
|
|
3868
|
+
: undefined);
|
|
3869
|
+
if (!message) return undefined;
|
|
3870
|
+
const part = partTarget?.message === message
|
|
3871
|
+
? partTarget.part
|
|
3872
|
+
: message.reasoning?.find((candidate) => candidate.id === reasoningId);
|
|
3873
|
+
return part ? { message, part } : undefined;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
private mountedCanonicalChat(
|
|
3877
|
+
projectionArg: ICanonicalChatProjection,
|
|
3878
|
+
): InstanceType<typeof plugins.deesCatalog.DeesHarnessChat> | undefined {
|
|
3879
|
+
const chat = this.shadowRoot?.querySelector('dees-harness-chat');
|
|
3880
|
+
return chat instanceof plugins.deesCatalog.DeesHarnessChat
|
|
3881
|
+
&& chat.messages === projectionArg.messages
|
|
3882
|
+
&& chat.transcriptKey === projectionArg.key
|
|
3883
|
+
? chat
|
|
3884
|
+
: undefined;
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
private applyCanonicalTextDelta(
|
|
3888
|
+
projectionArg: ICanonicalChatProjection,
|
|
3889
|
+
messageArg: plugins.deesCatalog.IHarnessMessage,
|
|
3890
|
+
deltaArg: string,
|
|
3891
|
+
): boolean {
|
|
3892
|
+
const previousText = messageArg.text;
|
|
3893
|
+
const expectedText = `${previousText}${deltaArg}`;
|
|
3894
|
+
const chat = this.mountedCanonicalChat(projectionArg);
|
|
3895
|
+
chat?.applyDelta({ type: 'text', messageId: messageArg.id, delta: deltaArg });
|
|
3896
|
+
if (messageArg.text === previousText) {
|
|
3897
|
+
messageArg.text = expectedText;
|
|
3898
|
+
messageArg.streaming = true;
|
|
3899
|
+
}
|
|
3900
|
+
if (messageArg.text === expectedText) return true;
|
|
3901
|
+
messageArg.text = previousText;
|
|
3902
|
+
return false;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
private applyCanonicalReasoningDelta(
|
|
3906
|
+
projectionArg: ICanonicalChatProjection,
|
|
3907
|
+
targetArg: ICanonicalReasoningTarget,
|
|
3908
|
+
deltaArg: string,
|
|
3909
|
+
): boolean {
|
|
3910
|
+
const previousText = targetArg.part.text;
|
|
3911
|
+
const expectedText = `${previousText}${deltaArg}`;
|
|
3912
|
+
const chat = this.mountedCanonicalChat(projectionArg);
|
|
3913
|
+
chat?.applyDelta({
|
|
3914
|
+
type: 'reasoning',
|
|
3915
|
+
messageId: targetArg.message.id,
|
|
3916
|
+
partId: targetArg.part.id,
|
|
3917
|
+
delta: deltaArg,
|
|
3918
|
+
});
|
|
3919
|
+
if (targetArg.part.text === previousText) targetArg.part.text = expectedText;
|
|
3920
|
+
if (targetArg.part.text === expectedText) return true;
|
|
3921
|
+
targetArg.part.text = previousText;
|
|
3922
|
+
return false;
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
private requestCanonicalChatReconciliation(structureMayChangeArg = true): void {
|
|
3926
|
+
this.canonicalChatProjectionDirty = true;
|
|
3927
|
+
this.canonicalTimelineRefreshPending ||= structureMayChangeArg;
|
|
3928
|
+
this.requestUpdate();
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3262
3931
|
private applyLiveToolExecution(
|
|
3263
3932
|
projectIdArg: string,
|
|
3264
3933
|
executionArg: interfaces.IControllerToolExecution,
|
|
@@ -3332,24 +4001,22 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3332
4001
|
}
|
|
3333
4002
|
this.reconcileSubtaskPreviews(projectIdArg, executionArg.sessionId);
|
|
3334
4003
|
|
|
3335
|
-
const
|
|
4004
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, executionArg.sessionId);
|
|
4005
|
+
if (projection && this.canonicalTimelineRefreshPending) this.requestUpdate();
|
|
3336
4006
|
const messageId = controllerRuntimeIdToUiKey(execution.partId);
|
|
3337
4007
|
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)!;
|
|
4008
|
+
const message = projection?.messagesById.get(messageId);
|
|
4009
|
+
if (projection && message) {
|
|
3343
4010
|
message.toolCall = harnessToolCall;
|
|
3344
4011
|
message.streaming = execution.status === 'pending' || execution.status === 'running';
|
|
3345
4012
|
message.updatedAt = execution.finishedAt ?? execution.sourceUpdatedAt;
|
|
3346
|
-
|
|
4013
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
3347
4014
|
type: 'tool-update',
|
|
3348
4015
|
messageId,
|
|
3349
4016
|
tool: harnessToolCall,
|
|
3350
4017
|
});
|
|
3351
4018
|
} else {
|
|
3352
|
-
this.
|
|
4019
|
+
this.requestCanonicalChatReconciliation();
|
|
3353
4020
|
}
|
|
3354
4021
|
}
|
|
3355
4022
|
|
|
@@ -3388,23 +4055,59 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3388
4055
|
if (cursorArg.streamEpoch < (this.liveMessageStreamEpochs.get(harnessId) ?? 0)) return false;
|
|
3389
4056
|
if (!this.acceptLiveMessageEpoch(harnessId, cursorArg.streamEpoch, false)) return false;
|
|
3390
4057
|
const sessionKey = this.liveToolSessionKey(projectIdArg, sessionIdArg);
|
|
4058
|
+
const deltaBlockKey = this.sessionOperationKey(projectIdArg, sessionIdArg);
|
|
4059
|
+
const recoveringDeltaGap = this.liveMessageDeltaBlocks.get(deltaBlockKey)?.streamEpoch
|
|
4060
|
+
=== cursorArg.streamEpoch;
|
|
3391
4061
|
this.liveMessageHydrationFloors.set(sessionKey, { sessionId: sessionIdArg, cursor: cursorArg });
|
|
3392
4062
|
for (const [key, update] of this.liveReasoningUpdates) {
|
|
4063
|
+
const target = this.authoritativeReasoningTarget(update, detailArg);
|
|
3393
4064
|
if (
|
|
3394
4065
|
key.startsWith(`${projectIdArg}\0`)
|
|
3395
4066
|
&& controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
3396
4067
|
&& this.hydrationCursorCovers(cursorArg, update)
|
|
3397
|
-
&&
|
|
3398
|
-
|
|
4068
|
+
&& target !== undefined
|
|
4069
|
+
&& (recoveringDeltaGap || this.authoritativeReasoningUpdateCovers(update, detailArg))
|
|
4070
|
+
) this.retireLiveReasoningUpdateToBaseline(key, update, target.part.text, cursorArg);
|
|
3399
4071
|
}
|
|
3400
4072
|
for (const [key, update] of this.liveTextUpdates) {
|
|
4073
|
+
const target = this.authoritativeTextTarget(update, detailArg);
|
|
3401
4074
|
if (
|
|
3402
4075
|
key.startsWith(`${projectIdArg}\0`)
|
|
3403
4076
|
&& controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
3404
4077
|
&& this.hydrationCursorCovers(cursorArg, update)
|
|
3405
|
-
&&
|
|
3406
|
-
|
|
4078
|
+
&& target !== undefined
|
|
4079
|
+
&& (recoveringDeltaGap || this.authoritativeTextUpdateCovers(update, detailArg))
|
|
4080
|
+
) this.retireLiveTextUpdateToBaseline(key, update, target.text, cursorArg);
|
|
3407
4081
|
}
|
|
4082
|
+
for (const [key, baseline] of [...this.liveReasoningBaselines]) {
|
|
4083
|
+
const update = baseline.update;
|
|
4084
|
+
if (
|
|
4085
|
+
!key.startsWith(`${projectIdArg}\0`)
|
|
4086
|
+
|| !controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
4087
|
+
|| !this.hydrationCursorCovers(cursorArg, update)
|
|
4088
|
+
) continue;
|
|
4089
|
+
const target = this.authoritativeReasoningTarget(update, detailArg);
|
|
4090
|
+
if (target) {
|
|
4091
|
+
this.retireLiveReasoningUpdateToBaseline(key, update, target.part.text, cursorArg);
|
|
4092
|
+
} else if (recoveringDeltaGap) {
|
|
4093
|
+
this.deleteLiveReasoningBaseline(key);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
for (const [key, baseline] of [...this.liveTextBaselines]) {
|
|
4097
|
+
const update = baseline.update;
|
|
4098
|
+
if (
|
|
4099
|
+
!key.startsWith(`${projectIdArg}\0`)
|
|
4100
|
+
|| !controllerRuntimeIdsEqual(update.sessionId, sessionIdArg)
|
|
4101
|
+
|| !this.hydrationCursorCovers(cursorArg, update)
|
|
4102
|
+
) continue;
|
|
4103
|
+
const target = this.authoritativeTextTarget(update, detailArg);
|
|
4104
|
+
if (target) {
|
|
4105
|
+
this.retireLiveTextUpdateToBaseline(key, update, target.text, cursorArg);
|
|
4106
|
+
} else if (recoveringDeltaGap) {
|
|
4107
|
+
this.deleteLiveTextBaseline(key);
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
this.liveMessageDeltaBlocks.delete(deltaBlockKey);
|
|
3408
4111
|
return true;
|
|
3409
4112
|
}
|
|
3410
4113
|
|
|
@@ -3448,6 +4151,30 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3448
4151
|
|| !controllerRuntimeIdsEqual(update.sessionId, sessionId)
|
|
3449
4152
|
) this.deleteLiveTextUpdate(key);
|
|
3450
4153
|
}
|
|
4154
|
+
for (const [key, baseline] of this.liveReasoningBaselines) {
|
|
4155
|
+
if (
|
|
4156
|
+
!projectId
|
|
4157
|
+
|| !isSessionRuntimeId(sessionId)
|
|
4158
|
+
|| !key.startsWith(`${projectId}\0`)
|
|
4159
|
+
|| !controllerRuntimeIdsEqual(baseline.update.sessionId, sessionId)
|
|
4160
|
+
) this.deleteLiveReasoningBaseline(key);
|
|
4161
|
+
}
|
|
4162
|
+
for (const [key, baseline] of this.liveTextBaselines) {
|
|
4163
|
+
if (
|
|
4164
|
+
!projectId
|
|
4165
|
+
|| !isSessionRuntimeId(sessionId)
|
|
4166
|
+
|| !key.startsWith(`${projectId}\0`)
|
|
4167
|
+
|| !controllerRuntimeIdsEqual(baseline.update.sessionId, sessionId)
|
|
4168
|
+
) this.deleteLiveTextBaseline(key);
|
|
4169
|
+
}
|
|
4170
|
+
for (const [key, block] of this.liveMessageDeltaBlocks) {
|
|
4171
|
+
if (
|
|
4172
|
+
!projectId
|
|
4173
|
+
|| !isSessionRuntimeId(sessionId)
|
|
4174
|
+
|| key !== this.sessionOperationKey(projectId, sessionId)
|
|
4175
|
+
|| !controllerRuntimeIdsEqual(block.sessionId, sessionId)
|
|
4176
|
+
) this.liveMessageDeltaBlocks.delete(key);
|
|
4177
|
+
}
|
|
3451
4178
|
}
|
|
3452
4179
|
|
|
3453
4180
|
private subtaskPreviewKey(
|
|
@@ -3667,7 +4394,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3667
4394
|
if (isBusy) this.queueSubtaskPreviewHydration(entry);
|
|
3668
4395
|
changed = true;
|
|
3669
4396
|
}
|
|
3670
|
-
if (changed) this.
|
|
4397
|
+
if (changed) this.requestCanonicalChatReconciliation();
|
|
3671
4398
|
}
|
|
3672
4399
|
|
|
3673
4400
|
private queueSubtaskPreviewHydration(entryArg: ISubtaskPreviewEntry): void {
|
|
@@ -3795,7 +4522,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3795
4522
|
messages,
|
|
3796
4523
|
...((page.truncated === true || page.nextCursor !== undefined) ? { truncated: true } : {}),
|
|
3797
4524
|
};
|
|
3798
|
-
this.
|
|
4525
|
+
this.requestCanonicalChatReconciliation();
|
|
3799
4526
|
} catch {
|
|
3800
4527
|
if (abortControllerArg.signal.aborted) return;
|
|
3801
4528
|
if (!this.subtaskPreviewEntryIsCurrent(entryArg)) return;
|
|
@@ -3807,7 +4534,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3807
4534
|
status: entryArg.stream.status,
|
|
3808
4535
|
notice: subtaskPreviewUnavailableNotice,
|
|
3809
4536
|
};
|
|
3810
|
-
this.
|
|
4537
|
+
this.requestCanonicalChatReconciliation();
|
|
3811
4538
|
}
|
|
3812
4539
|
}
|
|
3813
4540
|
|
|
@@ -3840,7 +4567,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3840
4567
|
this.queueSubtaskPreviewHydration(entry);
|
|
3841
4568
|
}, subtaskPreviewRefreshDelayMs);
|
|
3842
4569
|
}
|
|
3843
|
-
if (changed) this.
|
|
4570
|
+
if (changed) this.requestCanonicalChatReconciliation();
|
|
3844
4571
|
}
|
|
3845
4572
|
|
|
3846
4573
|
private clearSubtaskPreviewState(): void {
|
|
@@ -3851,6 +4578,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
3851
4578
|
}
|
|
3852
4579
|
this.subtaskPreviewEntries.clear();
|
|
3853
4580
|
this.subtaskPreviewHydrationQueue = [];
|
|
4581
|
+
this.canonicalChatProjectionDirty = true;
|
|
3854
4582
|
}
|
|
3855
4583
|
|
|
3856
4584
|
private applySessionEventState(eventArg: interfaces.IControllerEvent): void {
|
|
@@ -4001,7 +4729,9 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4001
4729
|
&& this.socketClient.isConnected
|
|
4002
4730
|
) {
|
|
4003
4731
|
this.refreshPending = false;
|
|
4004
|
-
|
|
4732
|
+
const includeRelated = this.refreshRelatedPending;
|
|
4733
|
+
this.refreshRelatedPending = false;
|
|
4734
|
+
await this.performSessionRefresh(includeRelated);
|
|
4005
4735
|
}
|
|
4006
4736
|
} finally {
|
|
4007
4737
|
this.refreshInFlight = undefined;
|
|
@@ -4041,7 +4771,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4041
4771
|
}
|
|
4042
4772
|
}
|
|
4043
4773
|
|
|
4044
|
-
private async performSessionRefresh(): Promise<void> {
|
|
4774
|
+
private async performSessionRefresh(includeRelatedArg = true): Promise<void> {
|
|
4045
4775
|
const projectId = this.selectedProjectId;
|
|
4046
4776
|
if (!isNonEmptyString(projectId)) {
|
|
4047
4777
|
this.sessions = [];
|
|
@@ -4062,10 +4792,14 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4062
4792
|
&& controllerRuntimeIdsEqual(sessionArg.id, selectedSessionAtStart)
|
|
4063
4793
|
&& sessionArg.archivedAt !== undefined
|
|
4064
4794
|
));
|
|
4065
|
-
if (selectedSessionAtStart)
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4795
|
+
if (includeRelatedArg && selectedSessionAtStart) {
|
|
4796
|
+
void this.refreshSelectedSessionDetail(false);
|
|
4797
|
+
}
|
|
4798
|
+
if (includeRelatedArg) {
|
|
4799
|
+
void this.loadSessionGroups();
|
|
4800
|
+
void this.loadTerminals();
|
|
4801
|
+
void this.loadResources();
|
|
4802
|
+
}
|
|
4069
4803
|
// The loading state is for a list we cannot show yet; background refreshes
|
|
4070
4804
|
// must not flip it, or the sidebar flashes on every pushed event.
|
|
4071
4805
|
const initialLoad = this.sessionsLoadedForProjectId !== projectId;
|
|
@@ -4091,11 +4825,12 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4091
4825
|
? response.sessions.filter(hasSessionRuntimeId)
|
|
4092
4826
|
: [];
|
|
4093
4827
|
this.applySessionSnapshot(projectId, nextSessions, requestEventRevision);
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4828
|
+
const reconciledSessions = reconcileKeyedJsonArray(
|
|
4829
|
+
this.sessions,
|
|
4830
|
+
nextSessions,
|
|
4831
|
+
(session) => controllerRuntimeIdToUiKey(session.id),
|
|
4832
|
+
);
|
|
4833
|
+
if (reconciledSessions !== this.sessions) this.sessions = reconciledSessions;
|
|
4099
4834
|
const selectedSessionBecameArchived = selectedSessionAtStart !== undefined
|
|
4100
4835
|
&& !selectedSessionWasArchived
|
|
4101
4836
|
&& this.sessions.some((sessionArg) => (
|
|
@@ -4171,6 +4906,107 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4171
4906
|
}
|
|
4172
4907
|
}
|
|
4173
4908
|
|
|
4909
|
+
private reconcileControllerMessages(
|
|
4910
|
+
currentArg: readonly interfaces.IControllerMessage[],
|
|
4911
|
+
nextArg: readonly interfaces.IControllerMessage[],
|
|
4912
|
+
): interfaces.IControllerMessage[] {
|
|
4913
|
+
return reconcileKeyedJsonArray(
|
|
4914
|
+
currentArg,
|
|
4915
|
+
nextArg,
|
|
4916
|
+
(message) => controllerRuntimeIdToUiKey(message.id),
|
|
4917
|
+
);
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
private reconcileControllerMessageBundles(
|
|
4921
|
+
currentArg: readonly interfaces.IControllerMessageBundle[],
|
|
4922
|
+
nextArg: readonly interfaces.IControllerMessageBundle[],
|
|
4923
|
+
): interfaces.IControllerMessageBundle[] {
|
|
4924
|
+
const currentByKey = new Map(currentArg.map((bundle) => [
|
|
4925
|
+
controllerRuntimeIdToUiKey(bundle.sourceMessageId),
|
|
4926
|
+
bundle,
|
|
4927
|
+
]));
|
|
4928
|
+
let unchanged = currentArg.length === nextArg.length;
|
|
4929
|
+
const bundles = nextArg.map((nextBundle, index) => {
|
|
4930
|
+
const current = currentByKey.get(controllerRuntimeIdToUiKey(nextBundle.sourceMessageId));
|
|
4931
|
+
if (!current) {
|
|
4932
|
+
unchanged = false;
|
|
4933
|
+
return nextBundle;
|
|
4934
|
+
}
|
|
4935
|
+
const messages = this.reconcileControllerMessages(current.messages, nextBundle.messages);
|
|
4936
|
+
const bundle = current.structuralDigest === nextBundle.structuralDigest
|
|
4937
|
+
&& messages === current.messages
|
|
4938
|
+
&& controllerRuntimeIdsEqual(current.sourceMessageId, nextBundle.sourceMessageId)
|
|
4939
|
+
? current
|
|
4940
|
+
: { ...nextBundle, messages };
|
|
4941
|
+
unchanged &&= bundle === currentArg[index];
|
|
4942
|
+
return bundle;
|
|
4943
|
+
});
|
|
4944
|
+
return unchanged ? currentArg as interfaces.IControllerMessageBundle[] : bundles;
|
|
4945
|
+
}
|
|
4946
|
+
|
|
4947
|
+
private reconcileSessionRenderDetail(
|
|
4948
|
+
currentArg: IControllerSessionRenderDetail | undefined,
|
|
4949
|
+
nextArg: IControllerSessionRenderDetail,
|
|
4950
|
+
): IControllerSessionRenderDetail {
|
|
4951
|
+
if (!currentArg || !controllerRuntimeIdsEqual(currentArg.session.id, nextArg.session.id)) {
|
|
4952
|
+
return nextArg;
|
|
4953
|
+
}
|
|
4954
|
+
const reuse = <TValue>(currentValueArg: TValue, nextValueArg: TValue): TValue => (
|
|
4955
|
+
controllerJsonValuesEqual(currentValueArg, nextValueArg) ? currentValueArg : nextValueArg
|
|
4956
|
+
);
|
|
4957
|
+
const messages = this.reconcileControllerMessages(currentArg.messages, nextArg.messages);
|
|
4958
|
+
const pendingPrompts = reconcileKeyedJsonArray(
|
|
4959
|
+
currentArg.pendingPrompts,
|
|
4960
|
+
nextArg.pendingPrompts,
|
|
4961
|
+
(prompt) => controllerRuntimeIdToUiKey(prompt.id),
|
|
4962
|
+
);
|
|
4963
|
+
const permissions = reconcileKeyedJsonArray(
|
|
4964
|
+
currentArg.permissions,
|
|
4965
|
+
nextArg.permissions,
|
|
4966
|
+
(permission) => controllerRuntimeIdToUiKey(permission.id),
|
|
4967
|
+
);
|
|
4968
|
+
const questions = reconcileKeyedJsonArray(
|
|
4969
|
+
currentArg.questions,
|
|
4970
|
+
nextArg.questions,
|
|
4971
|
+
(question) => controllerRuntimeIdToUiKey(question.id),
|
|
4972
|
+
);
|
|
4973
|
+
const todos = reconcileKeyedJsonArray(
|
|
4974
|
+
currentArg.todos,
|
|
4975
|
+
nextArg.todos,
|
|
4976
|
+
(todo, index) => todo.id ? controllerRuntimeIdToUiKey(todo.id) : `index:${index}`,
|
|
4977
|
+
);
|
|
4978
|
+
const intelligenceExchanges = reconcileKeyedJsonArray(
|
|
4979
|
+
currentArg.intelligenceExchanges,
|
|
4980
|
+
nextArg.intelligenceExchanges,
|
|
4981
|
+
(exchange) => exchange.id,
|
|
4982
|
+
);
|
|
4983
|
+
const detail: IControllerSessionRenderDetail = {
|
|
4984
|
+
...nextArg,
|
|
4985
|
+
session: reuse(currentArg.session, nextArg.session),
|
|
4986
|
+
messages,
|
|
4987
|
+
pendingPrompts,
|
|
4988
|
+
permissions,
|
|
4989
|
+
questions,
|
|
4990
|
+
todos,
|
|
4991
|
+
scratchpad: reuse(currentArg.scratchpad, nextArg.scratchpad),
|
|
4992
|
+
intelligenceExchanges,
|
|
4993
|
+
sessionMetrics: reuse(currentArg.sessionMetrics, nextArg.sessionMetrics),
|
|
4994
|
+
toolStreamCursor: reuse(currentArg.toolStreamCursor, nextArg.toolStreamCursor),
|
|
4995
|
+
messageStreamCursor: reuse(currentArg.messageStreamCursor, nextArg.messageStreamCursor),
|
|
4996
|
+
...(nextArg.modelChoice === undefined
|
|
4997
|
+
? {}
|
|
4998
|
+
: { modelChoice: reuse(currentArg.modelChoice, nextArg.modelChoice) }),
|
|
4999
|
+
};
|
|
5000
|
+
const current = currentArg as unknown as Record<string, unknown>;
|
|
5001
|
+
const reconciled = detail as unknown as Record<string, unknown>;
|
|
5002
|
+
const currentKeys = Object.keys(current);
|
|
5003
|
+
const nextKeys = Object.keys(reconciled);
|
|
5004
|
+
return currentKeys.length === nextKeys.length
|
|
5005
|
+
&& nextKeys.every((key) => Object.is(current[key], reconciled[key]))
|
|
5006
|
+
? currentArg
|
|
5007
|
+
: detail;
|
|
5008
|
+
}
|
|
5009
|
+
|
|
4174
5010
|
private async loadSessionDetail(
|
|
4175
5011
|
sessionIdArg: TBrowserSessionId,
|
|
4176
5012
|
restartEnrichmentArg = true,
|
|
@@ -4284,6 +5120,10 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4284
5120
|
));
|
|
4285
5121
|
provisionalBundleKeys.clear();
|
|
4286
5122
|
}
|
|
5123
|
+
accumulated.bundles = this.reconcileControllerMessageBundles(
|
|
5124
|
+
this.detailBundles,
|
|
5125
|
+
accumulated.bundles,
|
|
5126
|
+
);
|
|
4287
5127
|
const { messagePage, ...coreDetail } = response;
|
|
4288
5128
|
const renderDetail: IControllerSessionRenderDetail = {
|
|
4289
5129
|
...coreDetail,
|
|
@@ -4356,9 +5196,9 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4356
5196
|
unavailable: this.detailHistoryLimits.unavailable,
|
|
4357
5197
|
};
|
|
4358
5198
|
}
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
5199
|
+
const reconciledDetail = this.reconcileSessionRenderDetail(this.sessionDetail, renderDetail);
|
|
5200
|
+
if (reconciledDetail !== this.sessionDetail) this.sessionDetail = reconciledDetail;
|
|
5201
|
+
this.ensureCanonicalChatProjection(reconciledDetail, projectId, sessionIdArg);
|
|
4362
5202
|
this.reconcileSubtaskPreviews(projectId, sessionIdArg);
|
|
4363
5203
|
// A draft that materialized keeps its placeholder view until this first
|
|
4364
5204
|
// real detail arrives; now the actual chat takes over.
|
|
@@ -4422,6 +5262,10 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4422
5262
|
this.detailHistoryOwnership = undefined;
|
|
4423
5263
|
this.detailHistoryLimits = emptyDetailHistoryLimits();
|
|
4424
5264
|
this.detailHistoryStatus = 'idle';
|
|
5265
|
+
this.canonicalChatProjection = undefined;
|
|
5266
|
+
this.canonicalChatProjectionDirty = false;
|
|
5267
|
+
this.canonicalTimelineRefreshPending = false;
|
|
5268
|
+
this.canonicalMessageRefreshIds.clear();
|
|
4425
5269
|
}
|
|
4426
5270
|
|
|
4427
5271
|
private invalidateDetailEnrichment(): void {
|
|
@@ -4551,14 +5395,19 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4551
5395
|
(bundle) => controllerRuntimeIdToUiKey(bundle.sourceMessageId),
|
|
4552
5396
|
);
|
|
4553
5397
|
if (provisionalContentChanged || !sameStringArray(previousKeys, nextKeys)) {
|
|
4554
|
-
this.detailBundles =
|
|
5398
|
+
this.detailBundles = this.reconcileControllerMessageBundles(
|
|
5399
|
+
this.detailBundles,
|
|
5400
|
+
bounded.bundles,
|
|
5401
|
+
);
|
|
4555
5402
|
this.detailBundleKeys = new Set(nextKeys);
|
|
4556
5403
|
const detail = this.sessionDetail;
|
|
4557
5404
|
if (!detail || !controllerRuntimeIdsEqual(detail.session.id, ownership.sessionId)) return;
|
|
4558
|
-
this.
|
|
5405
|
+
const nextDetail = this.reconcileSessionRenderDetail(detail, {
|
|
4559
5406
|
...detail,
|
|
4560
|
-
messages:
|
|
4561
|
-
};
|
|
5407
|
+
messages: this.detailBundles.flatMap((bundle) => bundle.messages),
|
|
5408
|
+
});
|
|
5409
|
+
if (nextDetail !== detail) this.sessionDetail = nextDetail;
|
|
5410
|
+
this.ensureCanonicalChatProjection(nextDetail, ownership.projectId, ownership.sessionId);
|
|
4562
5411
|
}
|
|
4563
5412
|
this.detailHistoryCursor = nextCursor;
|
|
4564
5413
|
if (nextCursor !== undefined) this.detailHistorySeenCursors.add(nextCursor);
|
|
@@ -4636,6 +5485,18 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4636
5485
|
const resourceId = eventArg.detail?.resource?.id;
|
|
4637
5486
|
const resource = this.resources.find((candidate) => candidate.id === resourceId);
|
|
4638
5487
|
if (!resource || resource.lifecycle !== 'active') return;
|
|
5488
|
+
if (
|
|
5489
|
+
resource.kind === 'browser'
|
|
5490
|
+
&& this.selectedResourceId === resource.id
|
|
5491
|
+
&& (
|
|
5492
|
+
this.browserViewLoading
|
|
5493
|
+
|| (
|
|
5494
|
+
this.browserViewResourceId === resource.id
|
|
5495
|
+
&& this.browserViewAttachmentRevision === resource.attachment.revision
|
|
5496
|
+
&& this.browserViewId !== ''
|
|
5497
|
+
)
|
|
5498
|
+
)
|
|
5499
|
+
) return;
|
|
4639
5500
|
this.invalidateSlashCatalog();
|
|
4640
5501
|
this.selectedResourceId = resource.id;
|
|
4641
5502
|
if (resource.kind === 'browser') {
|
|
@@ -4671,6 +5532,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4671
5532
|
this.browserViewLoading = true;
|
|
4672
5533
|
this.workspaceError = '';
|
|
4673
5534
|
let openedViewId = '';
|
|
5535
|
+
let openedStreamGeneration = -1;
|
|
4674
5536
|
try {
|
|
4675
5537
|
const response = await this.socketClient.fire<interfaces.IReq_ControllerBrowserViewOpen>(
|
|
4676
5538
|
'controller.browser.view.open',
|
|
@@ -4682,6 +5544,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4682
5544
|
{ maxRetries: 0, abortSignal: openAbortController.signal },
|
|
4683
5545
|
);
|
|
4684
5546
|
openedViewId = response.viewId;
|
|
5547
|
+
openedStreamGeneration = response.streamGeneration;
|
|
4685
5548
|
if (
|
|
4686
5549
|
generation !== this.browserViewGeneration
|
|
4687
5550
|
|| connectionGeneration !== this.connectionGeneration
|
|
@@ -4689,7 +5552,11 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4689
5552
|
|| response.resourceId !== resourceArg.id
|
|
4690
5553
|
|| response.attachmentRevision !== resourceArg.attachment.revision
|
|
4691
5554
|
) {
|
|
4692
|
-
void this.requestBrowserViewClose(
|
|
5555
|
+
void this.requestBrowserViewClose(
|
|
5556
|
+
response.viewId,
|
|
5557
|
+
response.streamGeneration,
|
|
5558
|
+
connectionGeneration,
|
|
5559
|
+
);
|
|
4693
5560
|
return;
|
|
4694
5561
|
}
|
|
4695
5562
|
if (
|
|
@@ -4698,29 +5565,49 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4698
5565
|
) this.browserViewPendingClose = undefined;
|
|
4699
5566
|
this.browserViewId = response.viewId;
|
|
4700
5567
|
this.browserViewConnectionGeneration = connectionGeneration;
|
|
5568
|
+
this.browserViewStreamGeneration = response.streamGeneration;
|
|
5569
|
+
this.browserViewActivated = false;
|
|
4701
5570
|
this.browserViewResourceId = response.resourceId;
|
|
4702
5571
|
this.browserViewAttachmentRevision = response.attachmentRevision;
|
|
4703
|
-
this.
|
|
5572
|
+
this.applyBrowserViewState(response.state);
|
|
5573
|
+
const transport = this.createBrowserViewTransportClient(
|
|
5574
|
+
response.streamGeneration,
|
|
5575
|
+
response.events,
|
|
5576
|
+
response.operations,
|
|
5577
|
+
generation,
|
|
5578
|
+
);
|
|
5579
|
+
this.browserViewTransport = transport;
|
|
5580
|
+
const transportStart = transport.start();
|
|
4704
5581
|
this.browserViewLoading = false;
|
|
4705
5582
|
this.requestUpdate();
|
|
4706
|
-
await this.updateComplete;
|
|
5583
|
+
await Promise.all([this.updateComplete, transportStart]);
|
|
4707
5584
|
const rendererReady = await this.mountBrowserRenderer(generation);
|
|
4708
5585
|
if (
|
|
4709
5586
|
!rendererReady
|
|
4710
5587
|
|| generation !== this.browserViewGeneration
|
|
4711
5588
|
|| connectionGeneration !== this.connectionGeneration
|
|
4712
5589
|
|| response.viewId !== this.browserViewId
|
|
5590
|
+
|| response.streamGeneration !== this.browserViewStreamGeneration
|
|
4713
5591
|
) return;
|
|
4714
5592
|
await this.socketClient.fire<interfaces.IReq_ControllerBrowserViewActivate>(
|
|
4715
5593
|
'controller.browser.view.activate',
|
|
4716
|
-
{ viewId: response.viewId },
|
|
5594
|
+
{ viewId: response.viewId, streamGeneration: response.streamGeneration },
|
|
4717
5595
|
{ maxRetries: 0, abortSignal: openAbortController.signal },
|
|
4718
5596
|
);
|
|
5597
|
+
if (
|
|
5598
|
+
generation === this.browserViewGeneration
|
|
5599
|
+
&& response.viewId === this.browserViewId
|
|
5600
|
+
&& response.streamGeneration === this.browserViewStreamGeneration
|
|
5601
|
+
) this.browserViewActivated = true;
|
|
4719
5602
|
} catch (errorArg) {
|
|
4720
5603
|
if (openedViewId && openedViewId === this.browserViewId) {
|
|
4721
5604
|
await this.closeBrowserView(false);
|
|
4722
5605
|
} else if (openedViewId && this.socketClient.isConnected) {
|
|
4723
|
-
await this.requestBrowserViewClose(
|
|
5606
|
+
await this.requestBrowserViewClose(
|
|
5607
|
+
openedViewId,
|
|
5608
|
+
openedStreamGeneration,
|
|
5609
|
+
connectionGeneration,
|
|
5610
|
+
);
|
|
4724
5611
|
}
|
|
4725
5612
|
if (
|
|
4726
5613
|
generation === this.browserViewGeneration
|
|
@@ -4743,114 +5630,275 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4743
5630
|
this.browserViewOpenAbortController = undefined;
|
|
4744
5631
|
this.browserNavigationAbortController?.abort(new Error('Browser view changed.'));
|
|
4745
5632
|
this.browserNavigationAbortController = undefined;
|
|
5633
|
+
this.browserViewRecoveryAbortController?.abort(new Error('Browser view changed.'));
|
|
5634
|
+
this.browserViewRecoveryAbortController = undefined;
|
|
4746
5635
|
const renderer = this.browserRenderer;
|
|
4747
|
-
|
|
4748
|
-
await renderer.stop().then(
|
|
4749
|
-
() => {
|
|
4750
|
-
if (this.browserRenderer === renderer) this.browserRenderer = undefined;
|
|
4751
|
-
},
|
|
4752
|
-
(errorArg) => {
|
|
4753
|
-
this.workspaceError = `Browser renderer cleanup remains pending: ${errorMessage(errorArg)}`;
|
|
4754
|
-
},
|
|
4755
|
-
);
|
|
4756
|
-
}
|
|
5636
|
+
const transport = this.browserViewTransport;
|
|
4757
5637
|
const viewId = this.browserViewId;
|
|
5638
|
+
const viewStreamGeneration = this.browserViewStreamGeneration;
|
|
4758
5639
|
const viewConnectionGeneration = this.browserViewConnectionGeneration;
|
|
4759
5640
|
this.browserViewId = '';
|
|
4760
5641
|
this.browserViewConnectionGeneration = -1;
|
|
5642
|
+
this.browserViewStreamGeneration = -1;
|
|
5643
|
+
this.browserViewActivated = false;
|
|
4761
5644
|
this.browserViewResourceId = '';
|
|
4762
5645
|
this.browserViewAttachmentRevision = -1;
|
|
4763
5646
|
this.browserViewState = undefined;
|
|
5647
|
+
this.browserAddressDraft = '';
|
|
5648
|
+
this.browserAddressEditing = false;
|
|
4764
5649
|
this.browserViewLoading = false;
|
|
4765
5650
|
this.browserViewListeners.clear();
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
5651
|
+
const remoteCloseTask = (
|
|
5652
|
+
viewId
|
|
5653
|
+
&& viewStreamGeneration > 0
|
|
5654
|
+
&& viewConnectionGeneration === this.connectionGeneration
|
|
5655
|
+
)
|
|
5656
|
+
? this.requestBrowserViewClose(
|
|
5657
|
+
viewId,
|
|
5658
|
+
viewStreamGeneration,
|
|
5659
|
+
viewConnectionGeneration,
|
|
5660
|
+
)
|
|
5661
|
+
: this.flushPendingBrowserViewClose();
|
|
5662
|
+
const rendererStopTask = renderer
|
|
5663
|
+
? this.stopBrowserRenderer(renderer)
|
|
5664
|
+
: Promise.resolve();
|
|
5665
|
+
const transportStopTask = transport
|
|
5666
|
+
? transport.stop(new Error('Browser view changed.')).then(() => {
|
|
5667
|
+
if (this.browserViewTransport === transport) this.browserViewTransport = undefined;
|
|
5668
|
+
})
|
|
5669
|
+
: Promise.resolve();
|
|
5670
|
+
await Promise.all([remoteCloseTask, rendererStopTask, transportStopTask]);
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5673
|
+
private async stopBrowserRenderer(rendererArg: plugins.LiveBrowserCanvasRenderer): Promise<void> {
|
|
5674
|
+
let stopTask: Promise<void>;
|
|
5675
|
+
try {
|
|
5676
|
+
stopTask = rendererArg.stop();
|
|
5677
|
+
} catch (errorArg) {
|
|
5678
|
+
if (this.browserRenderer === rendererArg) {
|
|
5679
|
+
this.workspaceError = `Browser renderer cleanup remains pending: ${errorMessage(errorArg)}`;
|
|
5680
|
+
}
|
|
5681
|
+
return;
|
|
5682
|
+
}
|
|
5683
|
+
void stopTask.then(() => {
|
|
5684
|
+
if (this.browserRenderer === rendererArg) this.browserRenderer = undefined;
|
|
5685
|
+
}, () => undefined);
|
|
5686
|
+
try {
|
|
5687
|
+
await waitForBrowserRendererLifecycle(stopTask, 'stop');
|
|
5688
|
+
} catch (errorArg) {
|
|
5689
|
+
if (this.browserRenderer === rendererArg) {
|
|
5690
|
+
this.workspaceError = `Browser renderer cleanup remains pending: ${errorMessage(errorArg)}`;
|
|
5691
|
+
}
|
|
4770
5692
|
}
|
|
4771
5693
|
}
|
|
4772
5694
|
|
|
4773
5695
|
private async requestBrowserViewClose(
|
|
4774
5696
|
viewIdArg: string,
|
|
5697
|
+
streamGenerationArg: number,
|
|
4775
5698
|
connectionGenerationArg: number,
|
|
4776
5699
|
): Promise<void> {
|
|
4777
5700
|
if (connectionGenerationArg !== this.connectionGeneration) return;
|
|
4778
|
-
const pendingClose = {
|
|
5701
|
+
const pendingClose = {
|
|
5702
|
+
viewId: viewIdArg,
|
|
5703
|
+
streamGeneration: streamGenerationArg,
|
|
5704
|
+
connectionGeneration: connectionGenerationArg,
|
|
5705
|
+
attempts: 0,
|
|
5706
|
+
};
|
|
4779
5707
|
this.browserViewPendingClose = pendingClose;
|
|
4780
5708
|
await this.flushPendingBrowserViewClose();
|
|
4781
5709
|
}
|
|
4782
5710
|
|
|
4783
|
-
private async flushPendingBrowserViewClose(): Promise<void> {
|
|
4784
|
-
const pendingClose = this.browserViewPendingClose;
|
|
5711
|
+
private async flushPendingBrowserViewClose(): Promise<void> {
|
|
5712
|
+
const pendingClose = this.browserViewPendingClose;
|
|
5713
|
+
if (
|
|
5714
|
+
!pendingClose
|
|
5715
|
+
|| pendingClose.connectionGeneration !== this.connectionGeneration
|
|
5716
|
+
|| !this.socketClient.isConnected
|
|
5717
|
+
) return;
|
|
5718
|
+
if (this.browserViewCloseTask) return this.browserViewCloseTask;
|
|
5719
|
+
pendingClose.attempts += 1;
|
|
5720
|
+
const closeTask = this.socketClient.fire<interfaces.IReq_ControllerBrowserViewClose>(
|
|
5721
|
+
'controller.browser.view.close',
|
|
5722
|
+
{
|
|
5723
|
+
viewId: pendingClose.viewId,
|
|
5724
|
+
expectedStreamGeneration: pendingClose.streamGeneration,
|
|
5725
|
+
},
|
|
5726
|
+
{ maxRetries: 0 },
|
|
5727
|
+
).then(() => {
|
|
5728
|
+
if (this.browserViewPendingClose === pendingClose) {
|
|
5729
|
+
this.browserViewPendingClose = undefined;
|
|
5730
|
+
}
|
|
5731
|
+
}).catch(() => {
|
|
5732
|
+
if (
|
|
5733
|
+
this.browserViewPendingClose === pendingClose
|
|
5734
|
+
&& pendingClose.attempts >= 3
|
|
5735
|
+
) {
|
|
5736
|
+
this.browserViewPendingClose = undefined;
|
|
5737
|
+
this.workspaceError = 'The browser view close request failed after three attempts.';
|
|
5738
|
+
return;
|
|
5739
|
+
}
|
|
5740
|
+
if (
|
|
5741
|
+
this.browserViewPendingClose !== pendingClose
|
|
5742
|
+
|| pendingClose.connectionGeneration !== this.connectionGeneration
|
|
5743
|
+
) return;
|
|
5744
|
+
if (this.browserViewCloseRetryTimer) clearTimeout(this.browserViewCloseRetryTimer);
|
|
5745
|
+
this.browserViewCloseRetryTimer = setTimeout(() => {
|
|
5746
|
+
this.browserViewCloseRetryTimer = undefined;
|
|
5747
|
+
void this.flushPendingBrowserViewClose();
|
|
5748
|
+
}, 250 * pendingClose.attempts);
|
|
5749
|
+
}).finally(() => {
|
|
5750
|
+
if (this.browserViewCloseTask === closeTask) this.browserViewCloseTask = undefined;
|
|
5751
|
+
if (
|
|
5752
|
+
this.browserViewPendingClose
|
|
5753
|
+
&& this.browserViewPendingClose !== pendingClose
|
|
5754
|
+
) void this.flushPendingBrowserViewClose();
|
|
5755
|
+
});
|
|
5756
|
+
this.browserViewCloseTask = closeTask;
|
|
5757
|
+
return closeTask;
|
|
5758
|
+
}
|
|
5759
|
+
|
|
5760
|
+
private createBrowserViewTransportClient(
|
|
5761
|
+
streamGenerationArg: number,
|
|
5762
|
+
eventsArg: plugins.typedrequestInterfaces.TVirtualStream<'receive'>,
|
|
5763
|
+
operationsArg: plugins.typedrequestInterfaces.TVirtualStream<'send'>,
|
|
5764
|
+
viewGenerationArg: number,
|
|
5765
|
+
): ControllerBrowserViewTransportClient {
|
|
5766
|
+
return new ControllerBrowserViewTransportClient({
|
|
5767
|
+
streamGeneration: streamGenerationArg,
|
|
5768
|
+
events: eventsArg,
|
|
5769
|
+
operations: operationsArg,
|
|
5770
|
+
onEvent: (eventArg) => {
|
|
5771
|
+
if (
|
|
5772
|
+
viewGenerationArg !== this.browserViewGeneration
|
|
5773
|
+
|| streamGenerationArg !== this.browserViewStreamGeneration
|
|
5774
|
+
) return;
|
|
5775
|
+
this.handleBrowserViewEvent(eventArg);
|
|
5776
|
+
},
|
|
5777
|
+
onFailure: (errorArg, recoverableArg) => {
|
|
5778
|
+
this.handleBrowserTransportFailure(
|
|
5779
|
+
viewGenerationArg,
|
|
5780
|
+
streamGenerationArg,
|
|
5781
|
+
errorArg,
|
|
5782
|
+
recoverableArg,
|
|
5783
|
+
);
|
|
5784
|
+
},
|
|
5785
|
+
});
|
|
5786
|
+
}
|
|
5787
|
+
|
|
5788
|
+
private handleBrowserViewEvent(eventArg: interfaces.TControllerBrowserViewEvent): void {
|
|
5789
|
+
if (eventArg.type === 'state') this.applyBrowserViewState(eventArg.state);
|
|
5790
|
+
if (eventArg.type === 'error' && eventArg.error.fatal) this.workspaceError = eventArg.error.message;
|
|
5791
|
+
for (const listener of this.browserViewListeners) listener(eventArg);
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5794
|
+
private applyBrowserViewState(stateArg: interfaces.IControllerBrowserViewState): boolean {
|
|
5795
|
+
if (this.browserViewState && stateArg.revision <= this.browserViewState.revision) return false;
|
|
5796
|
+
this.browserViewState = stateArg;
|
|
5797
|
+
if (!this.browserAddressEditing) {
|
|
5798
|
+
this.browserAddressDraft = stateArg.tabs.find((tabArg) => tabArg.active)?.url ?? '';
|
|
5799
|
+
}
|
|
5800
|
+
return true;
|
|
5801
|
+
}
|
|
5802
|
+
|
|
5803
|
+
private handleBrowserTransportFailure(
|
|
5804
|
+
viewGenerationArg: number,
|
|
5805
|
+
streamGenerationArg: number,
|
|
5806
|
+
errorArg: unknown,
|
|
5807
|
+
recoverableArg: boolean,
|
|
5808
|
+
): void {
|
|
4785
5809
|
if (
|
|
4786
|
-
|
|
4787
|
-
||
|
|
4788
|
-
|| !this.
|
|
5810
|
+
viewGenerationArg !== this.browserViewGeneration
|
|
5811
|
+
|| streamGenerationArg !== this.browserViewStreamGeneration
|
|
5812
|
+
|| !this.browserViewId
|
|
4789
5813
|
) return;
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
{ viewId: pendingClose.viewId },
|
|
4794
|
-
{ maxRetries: 0 },
|
|
4795
|
-
);
|
|
4796
|
-
} catch {
|
|
5814
|
+
if (!recoverableArg || !this.browserViewActivated || !this.browserRenderer) {
|
|
5815
|
+
this.workspaceError = `Browser transport failed: ${errorMessage(errorArg)}`;
|
|
5816
|
+
void this.closeBrowserView();
|
|
4797
5817
|
return;
|
|
4798
5818
|
}
|
|
4799
|
-
if (this.
|
|
5819
|
+
if (this.browserViewTransportRecoveryTask) return;
|
|
5820
|
+
const recovery = this.recoverBrowserViewTransport(
|
|
5821
|
+
viewGenerationArg,
|
|
5822
|
+
streamGenerationArg,
|
|
5823
|
+
errorArg,
|
|
5824
|
+
).finally(() => {
|
|
5825
|
+
if (this.browserViewTransportRecoveryTask === recovery) {
|
|
5826
|
+
this.browserViewTransportRecoveryTask = undefined;
|
|
5827
|
+
}
|
|
5828
|
+
});
|
|
5829
|
+
this.browserViewTransportRecoveryTask = recovery;
|
|
5830
|
+
void recovery.catch(() => undefined);
|
|
4800
5831
|
}
|
|
4801
5832
|
|
|
4802
|
-
private async
|
|
4803
|
-
|
|
5833
|
+
private async recoverBrowserViewTransport(
|
|
5834
|
+
viewGenerationArg: number,
|
|
5835
|
+
streamGenerationArg: number,
|
|
5836
|
+
reasonArg: unknown,
|
|
4804
5837
|
): Promise<void> {
|
|
4805
|
-
const
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
);
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
5838
|
+
const viewId = this.browserViewId;
|
|
5839
|
+
const connectionGeneration = this.connectionGeneration;
|
|
5840
|
+
const oldTransport = this.browserViewTransport;
|
|
5841
|
+
const renderer = this.browserRenderer;
|
|
5842
|
+
if (!viewId || !oldTransport || !renderer) return;
|
|
5843
|
+
const recoveryAbortController = new AbortController();
|
|
5844
|
+
this.browserViewRecoveryAbortController = recoveryAbortController;
|
|
5845
|
+
this.browserViewActivated = false;
|
|
5846
|
+
try {
|
|
5847
|
+
await waitForBrowserRendererLifecycle(renderer.suspend(), 'suspension');
|
|
5848
|
+
await oldTransport.stop(reasonArg);
|
|
5849
|
+
const response = await this.socketClient.fire<interfaces.IReq_ControllerBrowserViewRecover>(
|
|
5850
|
+
'controller.browser.view.recover',
|
|
5851
|
+
{ viewId, expectedStreamGeneration: streamGenerationArg },
|
|
5852
|
+
{ maxRetries: 0, abortSignal: recoveryAbortController.signal },
|
|
5853
|
+
);
|
|
5854
|
+
if (
|
|
5855
|
+
viewGenerationArg !== this.browserViewGeneration
|
|
5856
|
+
|| connectionGeneration !== this.connectionGeneration
|
|
5857
|
+
|| viewId !== this.browserViewId
|
|
5858
|
+
|| streamGenerationArg !== this.browserViewStreamGeneration
|
|
5859
|
+
|| response.viewId !== viewId
|
|
5860
|
+
|| response.resourceId !== this.browserViewResourceId
|
|
5861
|
+
|| response.attachmentRevision !== this.browserViewAttachmentRevision
|
|
5862
|
+
|| response.streamGeneration !== streamGenerationArg + 1
|
|
5863
|
+
) throw new Error('The recovered browser stream generation is stale.');
|
|
5864
|
+
const transport = this.createBrowserViewTransportClient(
|
|
5865
|
+
response.streamGeneration,
|
|
5866
|
+
response.events,
|
|
5867
|
+
response.operations,
|
|
5868
|
+
viewGenerationArg,
|
|
5869
|
+
);
|
|
5870
|
+
this.browserViewTransport = transport;
|
|
5871
|
+
this.browserViewStreamGeneration = response.streamGeneration;
|
|
5872
|
+
this.applyBrowserViewState(response.state);
|
|
5873
|
+
await transport.start();
|
|
5874
|
+
await waitForBrowserRendererLifecycle(renderer.resume(), 'resume');
|
|
5875
|
+
await this.socketClient.fire<interfaces.IReq_ControllerBrowserViewActivate>(
|
|
5876
|
+
'controller.browser.view.activate',
|
|
5877
|
+
{ viewId, streamGeneration: response.streamGeneration },
|
|
5878
|
+
{ maxRetries: 0, abortSignal: recoveryAbortController.signal },
|
|
5879
|
+
);
|
|
5880
|
+
if (
|
|
5881
|
+
viewGenerationArg === this.browserViewGeneration
|
|
5882
|
+
&& viewId === this.browserViewId
|
|
5883
|
+
&& response.streamGeneration === this.browserViewStreamGeneration
|
|
5884
|
+
) {
|
|
5885
|
+
this.browserViewActivated = true;
|
|
5886
|
+
this.workspaceError = '';
|
|
4821
5887
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
}
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
if (!isCurrent()) {
|
|
4835
|
-
await reject(new Error('The browser frame became stale during transfer.'));
|
|
4836
|
-
return;
|
|
4837
|
-
}
|
|
4838
|
-
data = firstChunk;
|
|
4839
|
-
await dataStream.accept();
|
|
4840
|
-
} catch (errorArg) {
|
|
4841
|
-
await reject(errorArg);
|
|
4842
|
-
throw errorArg;
|
|
5888
|
+
} catch (errorArg) {
|
|
5889
|
+
if (
|
|
5890
|
+
viewGenerationArg === this.browserViewGeneration
|
|
5891
|
+
&& connectionGeneration === this.connectionGeneration
|
|
5892
|
+
&& viewId === this.browserViewId
|
|
5893
|
+
) {
|
|
5894
|
+
this.workspaceError = `Browser transport recovery failed: ${errorMessage(errorArg)}`;
|
|
5895
|
+
await this.closeBrowserView();
|
|
5896
|
+
}
|
|
5897
|
+
} finally {
|
|
5898
|
+
if (this.browserViewRecoveryAbortController === recoveryAbortController) {
|
|
5899
|
+
this.browserViewRecoveryAbortController = undefined;
|
|
4843
5900
|
}
|
|
4844
|
-
if (!isCurrent()) return;
|
|
4845
|
-
const event = { type: 'frame' as const, frame: { ...frame, data } };
|
|
4846
|
-
for (const listener of this.browserViewListeners) listener(event);
|
|
4847
|
-
return;
|
|
4848
5901
|
}
|
|
4849
|
-
if (!isCurrent()) return;
|
|
4850
|
-
const event = messageArg.event;
|
|
4851
|
-
if (event.type === 'state') this.browserViewState = event.state;
|
|
4852
|
-
if (event.type === 'error' && event.error.fatal) this.workspaceError = event.error.message;
|
|
4853
|
-
for (const listener of this.browserViewListeners) listener(event);
|
|
4854
5902
|
}
|
|
4855
5903
|
|
|
4856
5904
|
private async browserViewOperation(
|
|
@@ -4859,14 +5907,19 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4859
5907
|
abortSignalArg?: AbortSignal,
|
|
4860
5908
|
): Promise<interfaces.IControllerBrowserViewState> {
|
|
4861
5909
|
const viewId = this.browserViewId;
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
5910
|
+
const transport = this.browserViewTransport;
|
|
5911
|
+
if (!viewId || !transport) throw new Error('The browser view is unavailable.');
|
|
5912
|
+
const result = await transport.operate(operationArg, abortSignalArg);
|
|
5913
|
+
if (
|
|
5914
|
+
result.status === 'succeeded'
|
|
5915
|
+
&& result.state
|
|
5916
|
+
&& viewId === this.browserViewId
|
|
5917
|
+
) {
|
|
5918
|
+
if (!publishStateArg) return result.state;
|
|
5919
|
+
this.applyBrowserViewState(result.state);
|
|
5920
|
+
}
|
|
5921
|
+
if (!this.browserViewState) throw new Error('The browser view state is unavailable.');
|
|
5922
|
+
return this.browserViewState;
|
|
4870
5923
|
}
|
|
4871
5924
|
|
|
4872
5925
|
private async navigateBrowserAddress(formArg: HTMLFormElement): Promise<void> {
|
|
@@ -4879,6 +5932,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4879
5932
|
const input = formArg.querySelector<HTMLInputElement>('input');
|
|
4880
5933
|
try {
|
|
4881
5934
|
const url = controllerNormalizeBrowserAddress(input?.value ?? '');
|
|
5935
|
+
this.browserAddressDraft = input?.value ?? '';
|
|
4882
5936
|
this.workspaceError = '';
|
|
4883
5937
|
const state = await this.browserViewOperation(
|
|
4884
5938
|
{ type: 'navigate', url },
|
|
@@ -4889,7 +5943,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4889
5943
|
requestId === this.browserNavigationRequestId
|
|
4890
5944
|
&& viewGeneration === this.browserViewGeneration
|
|
4891
5945
|
&& viewId === this.browserViewId
|
|
4892
|
-
) this.
|
|
5946
|
+
) this.applyBrowserViewState(state);
|
|
4893
5947
|
} catch (errorArg) {
|
|
4894
5948
|
if (
|
|
4895
5949
|
requestId === this.browserNavigationRequestId
|
|
@@ -4914,6 +5968,23 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4914
5968
|
if (form) void this.navigateBrowserAddress(form);
|
|
4915
5969
|
};
|
|
4916
5970
|
|
|
5971
|
+
private readonly handleBrowserAddressFocus = (eventArg: Event): void => {
|
|
5972
|
+
const input = eventArg.currentTarget;
|
|
5973
|
+
if (!(input instanceof HTMLInputElement)) return;
|
|
5974
|
+
this.browserAddressEditing = true;
|
|
5975
|
+
this.browserAddressDraft = input.value;
|
|
5976
|
+
};
|
|
5977
|
+
|
|
5978
|
+
private readonly handleBrowserAddressInput = (eventArg: Event): void => {
|
|
5979
|
+
const input = eventArg.currentTarget;
|
|
5980
|
+
if (input instanceof HTMLInputElement) this.browserAddressDraft = input.value;
|
|
5981
|
+
};
|
|
5982
|
+
|
|
5983
|
+
private readonly handleBrowserAddressBlur = (): void => {
|
|
5984
|
+
this.browserAddressEditing = false;
|
|
5985
|
+
this.requestUpdate();
|
|
5986
|
+
};
|
|
5987
|
+
|
|
4917
5988
|
private browserCanvasClient(): plugins.ILiveBrowserCanvasClient {
|
|
4918
5989
|
return {
|
|
4919
5990
|
getState: () => {
|
|
@@ -4925,12 +5996,13 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4925
5996
|
return () => this.browserViewListeners.delete(listenerArg);
|
|
4926
5997
|
},
|
|
4927
5998
|
acknowledgeFrame: async (acknowledgement, options) => {
|
|
4928
|
-
const
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
{
|
|
5999
|
+
const transport = this.browserViewTransport;
|
|
6000
|
+
if (!transport) throw new Error('The browser view is unavailable.');
|
|
6001
|
+
const result = await transport.operate(
|
|
6002
|
+
{ type: 'acknowledgeFrame', acknowledgement },
|
|
6003
|
+
options.signal,
|
|
4932
6004
|
);
|
|
4933
|
-
return { accepted:
|
|
6005
|
+
return { accepted: result.status === 'succeeded' && result.accepted === true };
|
|
4934
6006
|
},
|
|
4935
6007
|
setViewport: async (viewport, options) => {
|
|
4936
6008
|
await this.browserViewOperation({ type: 'setViewport', viewport }, true, options.signal);
|
|
@@ -4961,14 +6033,30 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
4961
6033
|
focusTarget: surface,
|
|
4962
6034
|
resizeTarget: surface,
|
|
4963
6035
|
onError: (errorArg) => {
|
|
4964
|
-
if (generationArg
|
|
6036
|
+
if (generationArg !== this.browserViewGeneration) return;
|
|
6037
|
+
this.workspaceError = errorArg.message;
|
|
6038
|
+
if (
|
|
6039
|
+
errorArg.code === 'frame_acknowledgement_failed'
|
|
6040
|
+
|| errorArg.code === 'frame_render_failed'
|
|
6041
|
+
) {
|
|
6042
|
+
this.handleBrowserTransportFailure(
|
|
6043
|
+
generationArg,
|
|
6044
|
+
this.browserViewStreamGeneration,
|
|
6045
|
+
errorArg,
|
|
6046
|
+
true,
|
|
6047
|
+
);
|
|
6048
|
+
}
|
|
4965
6049
|
},
|
|
4966
6050
|
});
|
|
4967
6051
|
this.browserRenderer = renderer;
|
|
4968
|
-
await
|
|
6052
|
+
await this.startBrowserRenderer(renderer);
|
|
4969
6053
|
return generationArg === this.browserViewGeneration && this.browserRenderer === renderer;
|
|
4970
6054
|
}
|
|
4971
6055
|
|
|
6056
|
+
private startBrowserRenderer(rendererArg: plugins.LiveBrowserCanvasRenderer): Promise<void> {
|
|
6057
|
+
return waitForBrowserRendererLifecycle(rendererArg.start(), 'start');
|
|
6058
|
+
}
|
|
6059
|
+
|
|
4972
6060
|
private readonly handleResourceAssociationRequest = (
|
|
4973
6061
|
eventArg: CustomEvent<plugins.deesCatalog.THarnessResourceAssociationRequestDetail>,
|
|
4974
6062
|
): void => {
|
|
@@ -6707,9 +7795,12 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
6707
7795
|
const terminals = Array.isArray(response.terminals)
|
|
6708
7796
|
? response.terminals.filter(hasTerminalRuntimeId)
|
|
6709
7797
|
: [];
|
|
6710
|
-
|
|
6711
|
-
this.terminals
|
|
6712
|
-
|
|
7798
|
+
const reconciledTerminals = reconcileKeyedJsonArray(
|
|
7799
|
+
this.terminals,
|
|
7800
|
+
terminals,
|
|
7801
|
+
(terminal) => controllerRuntimeIdToUiKey(terminal.id),
|
|
7802
|
+
);
|
|
7803
|
+
if (reconciledTerminals !== this.terminals) this.terminals = reconciledTerminals;
|
|
6713
7804
|
this.terminalsLoadedForProjectId = projectId;
|
|
6714
7805
|
if (
|
|
6715
7806
|
this.selectedTerminalId !== undefined
|
|
@@ -6751,7 +7842,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
6751
7842
|
) return;
|
|
6752
7843
|
this.resources = Array.isArray(response.resources) ? response.resources : [];
|
|
6753
7844
|
this.resourcesLoadedForProjectId = projectId;
|
|
6754
|
-
const viewedBrowserResource = this.resources.find((resource) => (
|
|
7845
|
+
const viewedBrowserResource = this.resources.find((resource): resource is interfaces.IControllerBrowserResource => (
|
|
6755
7846
|
resource.id === this.browserViewResourceId && resource.kind === 'browser'
|
|
6756
7847
|
));
|
|
6757
7848
|
if (
|
|
@@ -6761,7 +7852,22 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
6761
7852
|
|| viewedBrowserResource.lifecycle !== 'active'
|
|
6762
7853
|
|| viewedBrowserResource.attachment.revision !== this.browserViewAttachmentRevision
|
|
6763
7854
|
)
|
|
6764
|
-
)
|
|
7855
|
+
) {
|
|
7856
|
+
const reopen = Boolean(
|
|
7857
|
+
viewedBrowserResource
|
|
7858
|
+
&& viewedBrowserResource.lifecycle === 'active'
|
|
7859
|
+
&& viewedBrowserResource.browserRuntimeState === 'available'
|
|
7860
|
+
&& this.selectedResourceId === viewedBrowserResource.id,
|
|
7861
|
+
);
|
|
7862
|
+
await this.closeBrowserView();
|
|
7863
|
+
if (
|
|
7864
|
+
reopen
|
|
7865
|
+
&& viewedBrowserResource
|
|
7866
|
+
&& this.selectedResourceId === viewedBrowserResource.id
|
|
7867
|
+
&& this.selectedProjectId === projectId
|
|
7868
|
+
&& this.isCurrentConnection(generation)
|
|
7869
|
+
) await this.openBrowserView(viewedBrowserResource);
|
|
7870
|
+
}
|
|
6765
7871
|
if (
|
|
6766
7872
|
this.selectedResourceId
|
|
6767
7873
|
&& !this.resources.some((resource) => resource.id === this.selectedResourceId)
|
|
@@ -6981,6 +8087,17 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
6981
8087
|
public updated(changedPropertiesArg: Map<PropertyKey, unknown>): void {
|
|
6982
8088
|
super.updated(changedPropertiesArg);
|
|
6983
8089
|
this.flushComposerFocus();
|
|
8090
|
+
const projection = this.canonicalChatProjection;
|
|
8091
|
+
const chat = projection ? this.mountedCanonicalChat(projection) : undefined;
|
|
8092
|
+
if (this.canonicalTimelineRefreshPending) {
|
|
8093
|
+
this.canonicalTimelineRefreshPending = false;
|
|
8094
|
+
this.canonicalMessageRefreshIds.clear();
|
|
8095
|
+
chat?.refreshMessages();
|
|
8096
|
+
} else if (this.canonicalMessageRefreshIds.size > 0) {
|
|
8097
|
+
const messageIds = [...this.canonicalMessageRefreshIds];
|
|
8098
|
+
this.canonicalMessageRefreshIds.clear();
|
|
8099
|
+
chat?.refreshMessages(messageIds);
|
|
8100
|
+
}
|
|
6984
8101
|
if (
|
|
6985
8102
|
changedPropertiesArg.has('selectedProjectId')
|
|
6986
8103
|
|| changedPropertiesArg.has('selectedSessionId')
|
|
@@ -7933,6 +9050,417 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
7933
9050
|
}
|
|
7934
9051
|
}
|
|
7935
9052
|
|
|
9053
|
+
private liveMessageOrdersEqual(
|
|
9054
|
+
leftArg: interfaces.IControllerTranscriptOrder | undefined,
|
|
9055
|
+
rightArg: interfaces.IControllerTranscriptOrder | undefined,
|
|
9056
|
+
): boolean {
|
|
9057
|
+
return leftArg === undefined
|
|
9058
|
+
? rightArg === undefined
|
|
9059
|
+
: rightArg !== undefined
|
|
9060
|
+
&& leftArg.messageIndex === rightArg.messageIndex
|
|
9061
|
+
&& leftArg.partIndex === rightArg.partIndex;
|
|
9062
|
+
}
|
|
9063
|
+
|
|
9064
|
+
private liveMessageIdentityMatches(
|
|
9065
|
+
previousArg: interfaces.IControllerReasoningUpdate | interfaces.IControllerTextUpdate,
|
|
9066
|
+
deltaArg: interfaces.IControllerReasoningDelta | interfaces.IControllerTextDelta,
|
|
9067
|
+
): boolean {
|
|
9068
|
+
return controllerRuntimeIdsEqual(previousArg.sessionId, deltaArg.sessionId)
|
|
9069
|
+
&& controllerRuntimeIdsEqual(previousArg.messageId, deltaArg.messageId)
|
|
9070
|
+
&& controllerRuntimeIdsEqual(previousArg.partId, deltaArg.partId)
|
|
9071
|
+
&& this.liveMessageOrdersEqual(previousArg.order, deltaArg.order);
|
|
9072
|
+
}
|
|
9073
|
+
|
|
9074
|
+
private liveMessageEventPassesBarriers(
|
|
9075
|
+
projectIdArg: string,
|
|
9076
|
+
updateArg: interfaces.IControllerReasoningUpdate
|
|
9077
|
+
| interfaces.IControllerTextUpdate
|
|
9078
|
+
| interfaces.IControllerReasoningDelta
|
|
9079
|
+
| interfaces.IControllerTextDelta,
|
|
9080
|
+
): { accepted: boolean; epochAdvanced: boolean } {
|
|
9081
|
+
if (!isSessionRuntimeId(updateArg.sessionId)) {
|
|
9082
|
+
return { accepted: false, epochAdvanced: false };
|
|
9083
|
+
}
|
|
9084
|
+
if (
|
|
9085
|
+
updateArg.sourceUpdatedAt
|
|
9086
|
+
<= (this.liveHistoryBarriers.get(
|
|
9087
|
+
this.liveToolSessionKey(projectIdArg, updateArg.sessionId),
|
|
9088
|
+
) ?? Number.NEGATIVE_INFINITY)
|
|
9089
|
+
) return { accepted: false, epochAdvanced: false };
|
|
9090
|
+
const previousEpoch = this.liveMessageStreamEpochs.get(updateArg.sessionId.harnessId) ?? 0;
|
|
9091
|
+
const accepted = this.acceptLiveMessageEpoch(
|
|
9092
|
+
updateArg.sessionId.harnessId,
|
|
9093
|
+
updateArg.streamEpoch,
|
|
9094
|
+
true,
|
|
9095
|
+
false,
|
|
9096
|
+
);
|
|
9097
|
+
return { accepted, epochAdvanced: accepted && updateArg.streamEpoch > previousEpoch };
|
|
9098
|
+
}
|
|
9099
|
+
|
|
9100
|
+
private refreshAfterTerminalLiveMessage(
|
|
9101
|
+
projectIdArg: string,
|
|
9102
|
+
sessionIdArg: TBrowserSessionId,
|
|
9103
|
+
): void {
|
|
9104
|
+
if (
|
|
9105
|
+
projectIdArg !== this.selectedProjectId
|
|
9106
|
+
|| !controllerRuntimeIdsEqual(sessionIdArg, this.selectedSessionId)
|
|
9107
|
+
) return;
|
|
9108
|
+
void this.refreshSelectedSessionDetail();
|
|
9109
|
+
void this.refreshSessions(false);
|
|
9110
|
+
}
|
|
9111
|
+
|
|
9112
|
+
private applyLiveReasoningUpdate(
|
|
9113
|
+
projectIdArg: string,
|
|
9114
|
+
updateArg: interfaces.IControllerReasoningUpdate,
|
|
9115
|
+
): void {
|
|
9116
|
+
if (!isSessionRuntimeId(updateArg.sessionId)) return;
|
|
9117
|
+
const sessionId = updateArg.sessionId;
|
|
9118
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, updateArg);
|
|
9119
|
+
if (!barrier.accepted) return;
|
|
9120
|
+
if (controllerRuntimeIdsEqual(updateArg.sessionId, this.subagentModalSessionId)) {
|
|
9121
|
+
this.scheduleSubagentModalRefresh();
|
|
9122
|
+
}
|
|
9123
|
+
if (
|
|
9124
|
+
projectIdArg !== this.selectedProjectId
|
|
9125
|
+
|| !controllerRuntimeIdsEqual(updateArg.sessionId, this.selectedSessionId)
|
|
9126
|
+
) return;
|
|
9127
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
9128
|
+
this.liveToolSessionKey(projectIdArg, updateArg.sessionId),
|
|
9129
|
+
)?.cursor;
|
|
9130
|
+
if (
|
|
9131
|
+
floor
|
|
9132
|
+
&& floor.streamEpoch === updateArg.streamEpoch
|
|
9133
|
+
&& updateArg.revision <= floor.revision
|
|
9134
|
+
) return;
|
|
9135
|
+
const key = this.liveMessageUpdateKey(projectIdArg, updateArg);
|
|
9136
|
+
const previous = this.liveReasoningUpdates.get(key)
|
|
9137
|
+
?? this.liveReasoningBaselines.get(key)?.update;
|
|
9138
|
+
if (
|
|
9139
|
+
previous
|
|
9140
|
+
&& previous.streamEpoch === updateArg.streamEpoch
|
|
9141
|
+
&& updateArg.revision <= previous.revision
|
|
9142
|
+
) return;
|
|
9143
|
+
if (!this.storeLiveReasoningUpdate(key, updateArg)) {
|
|
9144
|
+
this.blockLiveMessageDeltas(
|
|
9145
|
+
projectIdArg,
|
|
9146
|
+
sessionId,
|
|
9147
|
+
updateArg.streamEpoch,
|
|
9148
|
+
!barrier.epochAdvanced,
|
|
9149
|
+
);
|
|
9150
|
+
return;
|
|
9151
|
+
}
|
|
9152
|
+
|
|
9153
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
9154
|
+
if (projection && this.canonicalTimelineRefreshPending) this.requestUpdate();
|
|
9155
|
+
const target = projection ? this.canonicalReasoningTarget(projection, updateArg) : undefined;
|
|
9156
|
+
if (!projection || !target) {
|
|
9157
|
+
this.requestCanonicalChatReconciliation();
|
|
9158
|
+
} else if (!this.harnessMessageIsTerminal(target.message)) {
|
|
9159
|
+
const previousStreaming = target.message.streaming === true;
|
|
9160
|
+
const previousText = target.part.text;
|
|
9161
|
+
let correction = !updateArg.text.startsWith(previousText);
|
|
9162
|
+
if (!correction && updateArg.text.length > previousText.length) {
|
|
9163
|
+
correction = !this.applyCanonicalReasoningDelta(
|
|
9164
|
+
projection,
|
|
9165
|
+
target,
|
|
9166
|
+
updateArg.text.slice(previousText.length),
|
|
9167
|
+
);
|
|
9168
|
+
}
|
|
9169
|
+
if (correction) target.part.text = updateArg.text;
|
|
9170
|
+
target.part.startedAt = updateArg.sourceUpdatedAt;
|
|
9171
|
+
if (updateArg.status === 'running') delete target.part.endedAt;
|
|
9172
|
+
target.message.streaming = updateArg.status === 'running'
|
|
9173
|
+
|| (updateArg.sessionId.harnessId === 'opencode' && previousStreaming);
|
|
9174
|
+
if (
|
|
9175
|
+
!correction
|
|
9176
|
+
&& updateArg.status === 'running'
|
|
9177
|
+
&& !previousStreaming
|
|
9178
|
+
&& updateArg.text === previousText
|
|
9179
|
+
) {
|
|
9180
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
9181
|
+
type: 'reasoning',
|
|
9182
|
+
messageId: target.message.id,
|
|
9183
|
+
partId: target.part.id,
|
|
9184
|
+
delta: '',
|
|
9185
|
+
});
|
|
9186
|
+
}
|
|
9187
|
+
if (updateArg.status !== 'running') {
|
|
9188
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
9189
|
+
type: 'reasoning-end',
|
|
9190
|
+
messageId: target.message.id,
|
|
9191
|
+
partId: target.part.id,
|
|
9192
|
+
endedAt: updateArg.sourceUpdatedAt,
|
|
9193
|
+
});
|
|
9194
|
+
target.part.endedAt = updateArg.sourceUpdatedAt;
|
|
9195
|
+
}
|
|
9196
|
+
if (correction) {
|
|
9197
|
+
this.canonicalMessageRefreshIds.add(target.message.id);
|
|
9198
|
+
this.requestCanonicalChatReconciliation(false);
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
if (updateArg.status !== 'running') {
|
|
9202
|
+
this.refreshAfterTerminalLiveMessage(projectIdArg, sessionId);
|
|
9203
|
+
}
|
|
9204
|
+
}
|
|
9205
|
+
|
|
9206
|
+
private applyLiveTextUpdate(
|
|
9207
|
+
projectIdArg: string,
|
|
9208
|
+
updateArg: interfaces.IControllerTextUpdate,
|
|
9209
|
+
): void {
|
|
9210
|
+
if (!isSessionRuntimeId(updateArg.sessionId)) return;
|
|
9211
|
+
const sessionId = updateArg.sessionId;
|
|
9212
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, updateArg);
|
|
9213
|
+
if (!barrier.accepted) return;
|
|
9214
|
+
if (controllerRuntimeIdsEqual(updateArg.sessionId, this.subagentModalSessionId)) {
|
|
9215
|
+
this.scheduleSubagentModalRefresh();
|
|
9216
|
+
}
|
|
9217
|
+
if (
|
|
9218
|
+
projectIdArg !== this.selectedProjectId
|
|
9219
|
+
|| !controllerRuntimeIdsEqual(updateArg.sessionId, this.selectedSessionId)
|
|
9220
|
+
) return;
|
|
9221
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
9222
|
+
this.liveToolSessionKey(projectIdArg, updateArg.sessionId),
|
|
9223
|
+
)?.cursor;
|
|
9224
|
+
if (
|
|
9225
|
+
floor
|
|
9226
|
+
&& floor.streamEpoch === updateArg.streamEpoch
|
|
9227
|
+
&& updateArg.revision <= floor.revision
|
|
9228
|
+
) return;
|
|
9229
|
+
const key = this.liveMessageUpdateKey(projectIdArg, updateArg);
|
|
9230
|
+
const previous = this.liveTextUpdates.get(key) ?? this.liveTextBaselines.get(key)?.update;
|
|
9231
|
+
if (
|
|
9232
|
+
previous
|
|
9233
|
+
&& previous.streamEpoch === updateArg.streamEpoch
|
|
9234
|
+
&& updateArg.revision <= previous.revision
|
|
9235
|
+
) return;
|
|
9236
|
+
if (!this.storeLiveTextUpdate(key, updateArg)) {
|
|
9237
|
+
this.blockLiveMessageDeltas(
|
|
9238
|
+
projectIdArg,
|
|
9239
|
+
sessionId,
|
|
9240
|
+
updateArg.streamEpoch,
|
|
9241
|
+
!barrier.epochAdvanced,
|
|
9242
|
+
);
|
|
9243
|
+
return;
|
|
9244
|
+
}
|
|
9245
|
+
|
|
9246
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
9247
|
+
if (projection && this.canonicalTimelineRefreshPending) this.requestUpdate();
|
|
9248
|
+
const target = projection ? this.canonicalTextTarget(projection, updateArg) : undefined;
|
|
9249
|
+
if (!projection || !target) {
|
|
9250
|
+
this.requestCanonicalChatReconciliation();
|
|
9251
|
+
} else if (!this.harnessMessageIsTerminal(target)) {
|
|
9252
|
+
const previousStreaming = target.streaming === true;
|
|
9253
|
+
const previousText = target.text;
|
|
9254
|
+
let correction = !updateArg.text.startsWith(previousText);
|
|
9255
|
+
if (!correction && updateArg.text.length > previousText.length) {
|
|
9256
|
+
correction = !this.applyCanonicalTextDelta(
|
|
9257
|
+
projection,
|
|
9258
|
+
target,
|
|
9259
|
+
updateArg.text.slice(previousText.length),
|
|
9260
|
+
);
|
|
9261
|
+
}
|
|
9262
|
+
if (correction) target.text = updateArg.text;
|
|
9263
|
+
if (updateArg.status === 'running') {
|
|
9264
|
+
target.streaming = true;
|
|
9265
|
+
if (!correction && !previousStreaming && updateArg.text === previousText) {
|
|
9266
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
9267
|
+
type: 'text',
|
|
9268
|
+
messageId: target.id,
|
|
9269
|
+
delta: '',
|
|
9270
|
+
});
|
|
9271
|
+
}
|
|
9272
|
+
} else {
|
|
9273
|
+
this.mountedCanonicalChat(projection)?.applyDelta({
|
|
9274
|
+
type: 'message-end',
|
|
9275
|
+
messageId: target.id,
|
|
9276
|
+
});
|
|
9277
|
+
target.streaming = false;
|
|
9278
|
+
target.updatedAt = updateArg.sourceUpdatedAt;
|
|
9279
|
+
}
|
|
9280
|
+
if (correction) {
|
|
9281
|
+
this.canonicalMessageRefreshIds.add(target.id);
|
|
9282
|
+
this.requestCanonicalChatReconciliation(false);
|
|
9283
|
+
}
|
|
9284
|
+
}
|
|
9285
|
+
if (updateArg.status === 'completed') {
|
|
9286
|
+
this.refreshAfterTerminalLiveMessage(projectIdArg, sessionId);
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
9289
|
+
|
|
9290
|
+
private applyLiveReasoningDelta(
|
|
9291
|
+
projectIdArg: string,
|
|
9292
|
+
deltaArg: interfaces.IControllerReasoningDelta,
|
|
9293
|
+
): void {
|
|
9294
|
+
if (!isSessionRuntimeId(deltaArg.sessionId)) return;
|
|
9295
|
+
const sessionId = deltaArg.sessionId;
|
|
9296
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, deltaArg);
|
|
9297
|
+
if (!barrier.accepted) return;
|
|
9298
|
+
if (
|
|
9299
|
+
projectIdArg !== this.selectedProjectId
|
|
9300
|
+
|| !controllerRuntimeIdsEqual(deltaArg.sessionId, this.selectedSessionId)
|
|
9301
|
+
|| this.liveMessageDeltasBlocked(projectIdArg, sessionId, deltaArg.streamEpoch)
|
|
9302
|
+
) return;
|
|
9303
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
9304
|
+
this.liveToolSessionKey(projectIdArg, deltaArg.sessionId),
|
|
9305
|
+
)?.cursor;
|
|
9306
|
+
if (
|
|
9307
|
+
floor
|
|
9308
|
+
&& floor.streamEpoch === deltaArg.streamEpoch
|
|
9309
|
+
&& deltaArg.revision <= floor.revision
|
|
9310
|
+
) return;
|
|
9311
|
+
const key = this.liveMessageUpdateKey(projectIdArg, deltaArg);
|
|
9312
|
+
const active = this.liveReasoningUpdates.get(key);
|
|
9313
|
+
const baseline = this.liveReasoningBaselines.get(key);
|
|
9314
|
+
const previous = active ?? baseline?.update;
|
|
9315
|
+
if (
|
|
9316
|
+
previous
|
|
9317
|
+
&& previous.streamEpoch === deltaArg.streamEpoch
|
|
9318
|
+
&& deltaArg.revision <= previous.revision
|
|
9319
|
+
) return;
|
|
9320
|
+
const textUtf8Bytes = active
|
|
9321
|
+
? this.liveReasoningTextUtf8Bytes.get(key)
|
|
9322
|
+
?? browserTextEncoder.encode(active.text).byteLength
|
|
9323
|
+
: baseline?.textUtf8Bytes;
|
|
9324
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
9325
|
+
const target = projection ? this.canonicalReasoningTarget(projection, deltaArg) : undefined;
|
|
9326
|
+
const nextTextUtf8Bytes = previous && textUtf8Bytes !== undefined
|
|
9327
|
+
? controllerTextAfterAppendUtf8Bytes(previous.text, textUtf8Bytes, deltaArg.delta)
|
|
9328
|
+
: undefined;
|
|
9329
|
+
if (
|
|
9330
|
+
!previous
|
|
9331
|
+
|| previous.status !== 'running'
|
|
9332
|
+
|| previous.streamEpoch !== deltaArg.streamEpoch
|
|
9333
|
+
|| deltaArg.revision <= previous.revision
|
|
9334
|
+
|| deltaArg.sourceUpdatedAt < previous.sourceUpdatedAt
|
|
9335
|
+
|| !this.liveMessageIdentityMatches(previous, deltaArg)
|
|
9336
|
+
|| textUtf8Bytes !== deltaArg.baseTextUtf8Bytes
|
|
9337
|
+
|| nextTextUtf8Bytes !== deltaArg.textUtf8Bytes
|
|
9338
|
+
|| !projection
|
|
9339
|
+
|| !target
|
|
9340
|
+
|| this.harnessMessageIsTerminal(target.message)
|
|
9341
|
+
|| target.part.text !== previous.text
|
|
9342
|
+
|| (deltaArg.order !== undefined
|
|
9343
|
+
&& (!target.message.order
|
|
9344
|
+
|| transcriptOrderKey(target.message.order) !== transcriptOrderKey(deltaArg.order)))
|
|
9345
|
+
) {
|
|
9346
|
+
this.blockLiveMessageDeltas(
|
|
9347
|
+
projectIdArg,
|
|
9348
|
+
sessionId,
|
|
9349
|
+
deltaArg.streamEpoch,
|
|
9350
|
+
!barrier.epochAdvanced,
|
|
9351
|
+
);
|
|
9352
|
+
return;
|
|
9353
|
+
}
|
|
9354
|
+
if (!this.applyCanonicalReasoningDelta(projection, target, deltaArg.delta)) {
|
|
9355
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9356
|
+
return;
|
|
9357
|
+
}
|
|
9358
|
+
target.message.streaming = true;
|
|
9359
|
+
const update: interfaces.IControllerReasoningUpdate = {
|
|
9360
|
+
...previous,
|
|
9361
|
+
text: `${previous.text}${deltaArg.delta}`,
|
|
9362
|
+
sourceUpdatedAt: deltaArg.sourceUpdatedAt,
|
|
9363
|
+
revision: deltaArg.revision,
|
|
9364
|
+
streamEpoch: deltaArg.streamEpoch,
|
|
9365
|
+
};
|
|
9366
|
+
if (!this.storeLiveReasoningDeltaState(
|
|
9367
|
+
key,
|
|
9368
|
+
update,
|
|
9369
|
+
deltaArg.textUtf8Bytes,
|
|
9370
|
+
deltaArg.delta,
|
|
9371
|
+
)) {
|
|
9372
|
+
target.part.text = previous.text;
|
|
9373
|
+
this.requestCanonicalChatReconciliation();
|
|
9374
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9375
|
+
}
|
|
9376
|
+
}
|
|
9377
|
+
|
|
9378
|
+
private applyLiveTextDelta(
|
|
9379
|
+
projectIdArg: string,
|
|
9380
|
+
deltaArg: interfaces.IControllerTextDelta,
|
|
9381
|
+
): void {
|
|
9382
|
+
if (!isSessionRuntimeId(deltaArg.sessionId)) return;
|
|
9383
|
+
const sessionId = deltaArg.sessionId;
|
|
9384
|
+
const barrier = this.liveMessageEventPassesBarriers(projectIdArg, deltaArg);
|
|
9385
|
+
if (!barrier.accepted) return;
|
|
9386
|
+
if (
|
|
9387
|
+
projectIdArg !== this.selectedProjectId
|
|
9388
|
+
|| !controllerRuntimeIdsEqual(deltaArg.sessionId, this.selectedSessionId)
|
|
9389
|
+
|| this.liveMessageDeltasBlocked(projectIdArg, sessionId, deltaArg.streamEpoch)
|
|
9390
|
+
) return;
|
|
9391
|
+
const floor = this.liveMessageHydrationFloors.get(
|
|
9392
|
+
this.liveToolSessionKey(projectIdArg, deltaArg.sessionId),
|
|
9393
|
+
)?.cursor;
|
|
9394
|
+
if (
|
|
9395
|
+
floor
|
|
9396
|
+
&& floor.streamEpoch === deltaArg.streamEpoch
|
|
9397
|
+
&& deltaArg.revision <= floor.revision
|
|
9398
|
+
) return;
|
|
9399
|
+
const key = this.liveMessageUpdateKey(projectIdArg, deltaArg);
|
|
9400
|
+
const active = this.liveTextUpdates.get(key);
|
|
9401
|
+
const baseline = this.liveTextBaselines.get(key);
|
|
9402
|
+
const previous = active ?? baseline?.update;
|
|
9403
|
+
if (
|
|
9404
|
+
previous
|
|
9405
|
+
&& previous.streamEpoch === deltaArg.streamEpoch
|
|
9406
|
+
&& deltaArg.revision <= previous.revision
|
|
9407
|
+
) return;
|
|
9408
|
+
const textUtf8Bytes = active
|
|
9409
|
+
? this.liveTextTextUtf8Bytes.get(key)
|
|
9410
|
+
?? browserTextEncoder.encode(active.text).byteLength
|
|
9411
|
+
: baseline?.textUtf8Bytes;
|
|
9412
|
+
const projection = this.selectedCanonicalChatProjection(projectIdArg, sessionId);
|
|
9413
|
+
const target = projection ? this.canonicalTextTarget(projection, deltaArg) : undefined;
|
|
9414
|
+
const nextTextUtf8Bytes = previous && textUtf8Bytes !== undefined
|
|
9415
|
+
? controllerTextAfterAppendUtf8Bytes(previous.text, textUtf8Bytes, deltaArg.delta)
|
|
9416
|
+
: undefined;
|
|
9417
|
+
if (
|
|
9418
|
+
!previous
|
|
9419
|
+
|| previous.status !== 'running'
|
|
9420
|
+
|| previous.streamEpoch !== deltaArg.streamEpoch
|
|
9421
|
+
|| deltaArg.revision <= previous.revision
|
|
9422
|
+
|| deltaArg.sourceUpdatedAt < previous.sourceUpdatedAt
|
|
9423
|
+
|| !this.liveMessageIdentityMatches(previous, deltaArg)
|
|
9424
|
+
|| textUtf8Bytes !== deltaArg.baseTextUtf8Bytes
|
|
9425
|
+
|| nextTextUtf8Bytes !== deltaArg.textUtf8Bytes
|
|
9426
|
+
|| !projection
|
|
9427
|
+
|| !target
|
|
9428
|
+
|| this.harnessMessageIsTerminal(target)
|
|
9429
|
+
|| target.text !== previous.text
|
|
9430
|
+
|| (deltaArg.order !== undefined
|
|
9431
|
+
&& (!target.order || transcriptOrderKey(target.order) !== transcriptOrderKey(deltaArg.order)))
|
|
9432
|
+
) {
|
|
9433
|
+
this.blockLiveMessageDeltas(
|
|
9434
|
+
projectIdArg,
|
|
9435
|
+
sessionId,
|
|
9436
|
+
deltaArg.streamEpoch,
|
|
9437
|
+
!barrier.epochAdvanced,
|
|
9438
|
+
);
|
|
9439
|
+
return;
|
|
9440
|
+
}
|
|
9441
|
+
if (!this.applyCanonicalTextDelta(projection, target, deltaArg.delta)) {
|
|
9442
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9443
|
+
return;
|
|
9444
|
+
}
|
|
9445
|
+
const update: interfaces.IControllerTextUpdate = {
|
|
9446
|
+
...previous,
|
|
9447
|
+
text: `${previous.text}${deltaArg.delta}`,
|
|
9448
|
+
sourceUpdatedAt: deltaArg.sourceUpdatedAt,
|
|
9449
|
+
revision: deltaArg.revision,
|
|
9450
|
+
streamEpoch: deltaArg.streamEpoch,
|
|
9451
|
+
};
|
|
9452
|
+
if (!this.storeLiveTextDeltaState(
|
|
9453
|
+
key,
|
|
9454
|
+
update,
|
|
9455
|
+
deltaArg.textUtf8Bytes,
|
|
9456
|
+
deltaArg.delta,
|
|
9457
|
+
)) {
|
|
9458
|
+
target.text = previous.text;
|
|
9459
|
+
this.requestCanonicalChatReconciliation();
|
|
9460
|
+
this.blockLiveMessageDeltas(projectIdArg, sessionId, deltaArg.streamEpoch);
|
|
9461
|
+
}
|
|
9462
|
+
}
|
|
9463
|
+
|
|
7936
9464
|
private async handleControllerEvent(eventArg: interfaces.IControllerEvent): Promise<void> {
|
|
7937
9465
|
if (!this.authenticated) {
|
|
7938
9466
|
return;
|
|
@@ -8038,79 +9566,27 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
8038
9566
|
return;
|
|
8039
9567
|
}
|
|
8040
9568
|
if (eventArg.type === 'session.reasoning.updated' && eventArg.reasoningUpdate) {
|
|
8041
|
-
const update = eventArg.reasoningUpdate;
|
|
8042
9569
|
if (typeof eventArg.projectId !== 'string') return;
|
|
8043
|
-
if (!isSessionRuntimeId(
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
) return;
|
|
8050
|
-
|
|
8051
|
-
if (controllerRuntimeIdsEqual(update.sessionId, this.subagentModalSessionId)) {
|
|
8052
|
-
this.scheduleSubagentModalRefresh();
|
|
8053
|
-
}
|
|
8054
|
-
if (
|
|
8055
|
-
eventArg.projectId !== this.selectedProjectId
|
|
8056
|
-
|| !controllerRuntimeIdsEqual(update.sessionId, this.selectedSessionId)
|
|
8057
|
-
) return;
|
|
8058
|
-
const floor = this.liveMessageHydrationFloors.get(
|
|
8059
|
-
this.liveToolSessionKey(eventArg.projectId, update.sessionId),
|
|
8060
|
-
)?.cursor;
|
|
8061
|
-
if (
|
|
8062
|
-
floor
|
|
8063
|
-
&& floor.streamEpoch === update.streamEpoch
|
|
8064
|
-
&& update.revision <= floor.revision
|
|
8065
|
-
) return;
|
|
8066
|
-
const key = `${eventArg.projectId ?? ''}\0${controllerRuntimeIdToUiKey(update.sessionId)}\0${controllerRuntimeIdToUiKey(update.partId)}`;
|
|
8067
|
-
const previous = this.liveReasoningUpdates.get(key);
|
|
8068
|
-
if (
|
|
8069
|
-
!previous
|
|
8070
|
-
|| update.streamEpoch > previous.streamEpoch
|
|
8071
|
-
|| (update.streamEpoch === previous.streamEpoch && update.revision > previous.revision)
|
|
8072
|
-
) {
|
|
8073
|
-
if (this.storeLiveReasoningUpdate(key, update)) this.requestUpdate();
|
|
8074
|
-
}
|
|
8075
|
-
if (update.status !== 'running') this.refreshSessionsNow();
|
|
9570
|
+
if (!isSessionRuntimeId(eventArg.reasoningUpdate.sessionId)) return;
|
|
9571
|
+
this.applyLiveReasoningUpdate(eventArg.projectId, eventArg.reasoningUpdate);
|
|
9572
|
+
return;
|
|
9573
|
+
}
|
|
9574
|
+
if (eventArg.type === 'session.reasoning.delta' && eventArg.reasoningDelta) {
|
|
9575
|
+
if (typeof eventArg.projectId !== 'string') return;
|
|
9576
|
+
if (!isSessionRuntimeId(eventArg.reasoningDelta.sessionId)) return;
|
|
9577
|
+
this.applyLiveReasoningDelta(eventArg.projectId, eventArg.reasoningDelta);
|
|
8076
9578
|
return;
|
|
8077
9579
|
}
|
|
8078
9580
|
if (eventArg.type === 'session.text.updated' && eventArg.textUpdate) {
|
|
8079
|
-
const update = eventArg.textUpdate;
|
|
8080
9581
|
if (typeof eventArg.projectId !== 'string') return;
|
|
8081
|
-
if (!isSessionRuntimeId(
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
) return;
|
|
8088
|
-
|
|
8089
|
-
if (controllerRuntimeIdsEqual(update.sessionId, this.subagentModalSessionId)) {
|
|
8090
|
-
this.scheduleSubagentModalRefresh();
|
|
8091
|
-
}
|
|
8092
|
-
if (
|
|
8093
|
-
eventArg.projectId !== this.selectedProjectId
|
|
8094
|
-
|| !controllerRuntimeIdsEqual(update.sessionId, this.selectedSessionId)
|
|
8095
|
-
) return;
|
|
8096
|
-
const floor = this.liveMessageHydrationFloors.get(
|
|
8097
|
-
this.liveToolSessionKey(eventArg.projectId, update.sessionId),
|
|
8098
|
-
)?.cursor;
|
|
8099
|
-
if (
|
|
8100
|
-
floor
|
|
8101
|
-
&& floor.streamEpoch === update.streamEpoch
|
|
8102
|
-
&& update.revision <= floor.revision
|
|
8103
|
-
) return;
|
|
8104
|
-
const key = `${eventArg.projectId ?? ''}\0${controllerRuntimeIdToUiKey(update.sessionId)}\0${controllerRuntimeIdToUiKey(update.partId)}`;
|
|
8105
|
-
const previous = this.liveTextUpdates.get(key);
|
|
8106
|
-
if (
|
|
8107
|
-
!previous
|
|
8108
|
-
|| update.streamEpoch > previous.streamEpoch
|
|
8109
|
-
|| (update.streamEpoch === previous.streamEpoch && update.revision > previous.revision)
|
|
8110
|
-
) {
|
|
8111
|
-
if (this.storeLiveTextUpdate(key, update)) this.requestUpdate();
|
|
8112
|
-
}
|
|
8113
|
-
if (update.status === 'completed') this.refreshSessionsNow();
|
|
9582
|
+
if (!isSessionRuntimeId(eventArg.textUpdate.sessionId)) return;
|
|
9583
|
+
this.applyLiveTextUpdate(eventArg.projectId, eventArg.textUpdate);
|
|
9584
|
+
return;
|
|
9585
|
+
}
|
|
9586
|
+
if (eventArg.type === 'session.text.delta' && eventArg.textDelta) {
|
|
9587
|
+
if (typeof eventArg.projectId !== 'string') return;
|
|
9588
|
+
if (!isSessionRuntimeId(eventArg.textDelta.sessionId)) return;
|
|
9589
|
+
this.applyLiveTextDelta(eventArg.projectId, eventArg.textDelta);
|
|
8114
9590
|
return;
|
|
8115
9591
|
}
|
|
8116
9592
|
this.applySessionEventState(eventArg);
|
|
@@ -10424,25 +11900,6 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10424
11900
|
<dees-badge type="success" text="Connected" .rounded=${true}></dees-badge>
|
|
10425
11901
|
<span class="headerSpacer"></span>
|
|
10426
11902
|
<div class="headerActions">
|
|
10427
|
-
<dees-button
|
|
10428
|
-
class="refreshAction"
|
|
10429
|
-
type="ghost"
|
|
10430
|
-
size="sm"
|
|
10431
|
-
?disabled=${this.sessionsLoading || this.mutationPending}
|
|
10432
|
-
@clicked=${() => void this.refreshSessions()}
|
|
10433
|
-
>
|
|
10434
|
-
<dees-icon icon="lucide:RefreshCw" iconSize="14"></dees-icon>
|
|
10435
|
-
Refresh
|
|
10436
|
-
</dees-button>
|
|
10437
|
-
<dees-button
|
|
10438
|
-
type="ghost"
|
|
10439
|
-
size="sm"
|
|
10440
|
-
?disabled=${!this.selectedSessionId || this.mutationPending}
|
|
10441
|
-
@clicked=${this.showDeleteConfirmation}
|
|
10442
|
-
>
|
|
10443
|
-
<dees-icon icon="lucide:Trash2" iconSize="14"></dees-icon>
|
|
10444
|
-
Delete
|
|
10445
|
-
</dees-button>
|
|
10446
11903
|
<dees-button
|
|
10447
11904
|
type="ghost"
|
|
10448
11905
|
size="sm"
|
|
@@ -10746,9 +12203,12 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10746
12203
|
<dees-icon icon="lucide:Globe2" iconSize="16"></dees-icon>
|
|
10747
12204
|
<input
|
|
10748
12205
|
aria-label="Browser address"
|
|
10749
|
-
.value=${activeTab?.url ?? ''}
|
|
12206
|
+
.value=${this.browserAddressEditing ? this.browserAddressDraft : activeTab?.url ?? ''}
|
|
10750
12207
|
placeholder="https://example.com"
|
|
10751
12208
|
?disabled=${!this.browserViewId}
|
|
12209
|
+
@focus=${this.handleBrowserAddressFocus}
|
|
12210
|
+
@input=${this.handleBrowserAddressInput}
|
|
12211
|
+
@blur=${this.handleBrowserAddressBlur}
|
|
10752
12212
|
/>
|
|
10753
12213
|
<dees-button
|
|
10754
12214
|
type="highlighted"
|
|
@@ -10803,6 +12263,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10803
12263
|
.reasoningEffort=${this.resolveSessionEffortString() || 'default'}
|
|
10804
12264
|
.effortOptions=${this.resolveSessionEffortOptions()}
|
|
10805
12265
|
.showTodosPanel=${false}
|
|
12266
|
+
.markdownWhileStreaming=${false}
|
|
10806
12267
|
heading=${`New ${sessionHarnessLabel(this.draftHarnessId)} conversation`}
|
|
10807
12268
|
subheading=${directory
|
|
10808
12269
|
? `${sessionHarnessLabel(this.draftHarnessId)} creates this session on first send · starts in ${directory}`
|
|
@@ -10876,27 +12337,12 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10876
12337
|
const sessionId = detailArg.session.id;
|
|
10877
12338
|
const isOpenCode = sessionId.harnessId === 'opencode';
|
|
10878
12339
|
const readOnly = this.sessionIsReadOnly(sessionId);
|
|
10879
|
-
// The
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
.
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
for (const prompt of detailArg.pendingPrompts) {
|
|
10886
|
-
const id = controllerRuntimeIdToUiKey(prompt.id);
|
|
10887
|
-
if (visibleMessageIds.has(id)) continue;
|
|
10888
|
-
messages.push({
|
|
10889
|
-
id,
|
|
10890
|
-
role: 'user',
|
|
10891
|
-
text: prompt.text,
|
|
10892
|
-
createdAt: prompt.createdAt,
|
|
10893
|
-
markdown: false,
|
|
10894
|
-
attachments: prompt.attachments.map((attachment) => ({ ...attachment })),
|
|
10895
|
-
});
|
|
10896
|
-
}
|
|
10897
|
-
this.mergeLiveReasoningMessages(messages, this.selectedProjectId, sessionId);
|
|
10898
|
-
this.mergeLiveTextMessages(messages, this.selectedProjectId, sessionId);
|
|
10899
|
-
this.mergeLiveToolMessages(messages, this.selectedProjectId, sessionId);
|
|
12340
|
+
// The canonical array and all delta targets remain stable across unrelated app renders.
|
|
12341
|
+
const messages = this.ensureCanonicalChatProjection(
|
|
12342
|
+
detailArg,
|
|
12343
|
+
this.selectedProjectId,
|
|
12344
|
+
sessionId,
|
|
12345
|
+
).messages;
|
|
10900
12346
|
const status = this.toHarnessStatus(
|
|
10901
12347
|
detailArg.session,
|
|
10902
12348
|
this.activeSubagentCount(messages),
|
|
@@ -11120,6 +12566,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
11120
12566
|
.reasoningEffort=${this.resolveSessionEffortString(sessionId) || 'default'}
|
|
11121
12567
|
.effortOptions=${this.resolveSessionEffortOptions(sessionId)}
|
|
11122
12568
|
.showTodosPanel=${true}
|
|
12569
|
+
.markdownWhileStreaming=${false}
|
|
11123
12570
|
heading=${detailArg.session.title || `${sessionHarnessLabel(sessionId.harnessId)} session`}
|
|
11124
12571
|
subheading=${readOnly
|
|
11125
12572
|
? `Flex unavailable · saved projection · read-only · ${runtimeIdDisplay(sessionId)}`
|
|
@@ -11454,13 +12901,6 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
11454
12901
|
? (this.authenticated ? 'authenticated' : 'connected')
|
|
11455
12902
|
: pending ? 'connecting' : 'offline';
|
|
11456
12903
|
const metrics = this.systemMetrics;
|
|
11457
|
-
const memoryPercent = metrics?.memoryUsedBytes !== null
|
|
11458
|
-
&& metrics?.memoryUsedBytes !== undefined
|
|
11459
|
-
&& metrics.memoryTotalBytes !== null
|
|
11460
|
-
&& metrics.memoryTotalBytes !== undefined
|
|
11461
|
-
&& metrics.memoryTotalBytes > 0
|
|
11462
|
-
? (metrics.memoryUsedBytes / metrics.memoryTotalBytes) * 100
|
|
11463
|
-
: null;
|
|
11464
12904
|
const memoryTitle = metrics?.memoryUsedBytes !== null
|
|
11465
12905
|
&& metrics?.memoryUsedBytes !== undefined
|
|
11466
12906
|
&& metrics.memoryTotalBytes !== null
|
|
@@ -11484,19 +12924,19 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
11484
12924
|
: 'Whole-system CPU usage'}
|
|
11485
12925
|
>
|
|
11486
12926
|
<span class="metricLabel">CPU</span>
|
|
11487
|
-
<span
|
|
12927
|
+
<span class="metricValue">--</span>
|
|
11488
12928
|
</span>
|
|
11489
12929
|
<span class="statusMetric memoryMetric" title=${memoryTitle}>
|
|
11490
12930
|
<span class="metricLabel">RAM</span>
|
|
11491
|
-
<span
|
|
12931
|
+
<span class="metricValue">--</span>
|
|
11492
12932
|
</span>
|
|
11493
12933
|
<span
|
|
11494
12934
|
class="statusMetric networkMetric"
|
|
11495
12935
|
title="Aggregate traffic across all system network interfaces"
|
|
11496
12936
|
>
|
|
11497
12937
|
<span class="metricLabel">NET</span>
|
|
11498
|
-
<span>in
|
|
11499
|
-
<span>out
|
|
12938
|
+
<span class="networkReceive">in --</span>
|
|
12939
|
+
<span class="networkTransmit">out --</span>
|
|
11500
12940
|
</span>
|
|
11501
12941
|
</div>
|
|
11502
12942
|
`
|