@newheap/platform-ai-chat 0.4.0 → 0.6.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.
@@ -1,6 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, signal, Injectable, makeEnvironmentProviders } from '@angular/core';
3
- import { NH_ASSISTANT_CONFIG, normalizeNhAssistantClientContext, applyNhAssistantApprovalDecision, applyNhAssistantEvent, NH_ASSISTANT_FETCH } from '@newheap/platform-ai-chat';
2
+ import { InjectionToken, inject, signal, Injectable, DestroyRef, makeEnvironmentProviders } from '@angular/core';
3
+ import { NH_ASSISTANT_CONFIG, applyNhAssistantEvent, normalizeNhAssistantClientContext, applyNhAssistantApprovalDecision, NH_ASSISTANT_FETCH, NhAssistantLiveService } from '@newheap/platform-ai-chat';
4
+ import { Subject } from 'rxjs';
4
5
 
5
6
  const dashCase = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
7
  const styles = ['default', 'direct', 'personal', 'detailed'];
@@ -484,7 +485,10 @@ function hashText(text) {
484
485
  }
485
486
 
486
487
  const NH_ASSISTANT_MOCK_SCENARIO = new InjectionToken('NH_ASSISTANT_MOCK_SCENARIO');
488
+ /** Path the mock reports as hub; the mock live service answers it in memory. */
489
+ const NH_ASSISTANT_MOCK_HUB_PATH = '/hub/assistant-mock';
487
490
  const defaultLimits = { maxMessageChars: 4_000, maxToolCallsPerTurn: 8 };
491
+ const defaultUser = { actorId: 'mock-user', displayName: 'You' };
488
492
  class RunStopped extends Error {
489
493
  }
490
494
  /**
@@ -504,11 +508,20 @@ class NhAssistantMockBackend {
504
508
  this.enabledState = signal(this.scenario.enabled ?? true, ...(ngDevMode ? [{ debugName: "enabledState" }] : []));
505
509
  this.admin = new NhAssistantMockAdmin(this.scenario, () => new Date().toISOString());
506
510
  this.canAdministerState = signal(this.admin.administers, ...(ngDevMode ? [{ debugName: "canAdministerState" }] : []));
511
+ this.currentUser = this.scenario.collaboration?.currentUser ?? defaultUser;
512
+ this.members = new Map();
513
+ this.readPositions = new Map();
514
+ this.shareTokens = new Map();
515
+ this.activeActors = new Map();
516
+ this.liveSubject = new Subject();
517
+ this.pushEnabled = true;
507
518
  this.sequence = 0;
508
519
  /** Requests received so far, oldest first. */
509
520
  this.requests = [];
510
521
  this.enabled = this.enabledState.asReadonly();
511
522
  this.canAdminister = this.canAdministerState.asReadonly();
523
+ /** Live updates for the mock live service, in the order the hub would send them. */
524
+ this.live$ = this.liveSubject.asObservable();
512
525
  /** A `fetch` implementation that answers the assistant endpoints below `apiBaseUrl`. */
513
526
  this.fetch = async (input, init) => {
514
527
  const method = (init.method ?? 'GET').toUpperCase();
@@ -526,9 +539,49 @@ class NhAssistantMockBackend {
526
539
  return this.route(method, path, url.searchParams, body);
527
540
  };
528
541
  for (const conversation of this.scenario.conversations ?? []) {
529
- this.conversations.set(conversation.id, structuredClone(conversation));
542
+ const copy = withSequences(structuredClone(conversation));
543
+ this.conversations.set(copy.id, copy);
544
+ this.readPositions.set(copy.id, lastSequence(copy));
530
545
  }
531
546
  }
547
+ /**
548
+ * Plays a scripted turn as another person in the conversation, as if a colleague with an
549
+ * invitation sent a message: the conversation becomes shared, and the message and the
550
+ * answer arrive as live updates. Resolves when the turn ends.
551
+ */
552
+ async simulateParticipantTurn(conversationId, person, text) {
553
+ const conversation = this.conversations.get(conversationId);
554
+ if (!conversation || conversation.status !== 'idle') {
555
+ return;
556
+ }
557
+ this.addMember(conversationId, person, 'participant');
558
+ const userMessage = {
559
+ id: this.nextId(),
560
+ role: 'user',
561
+ createdAt: new Date().toISOString(),
562
+ parts: [{ type: 'text', text }],
563
+ authorActorId: person.actorId
564
+ };
565
+ this.activeActors.set(conversationId, person.actorId);
566
+ this.update(conversationId, current => ({ ...current, status: 'running', messages: [...current.messages, userMessage] }));
567
+ const stored = this.conversations.get(conversationId).messages.find(message => message.id === userMessage.id);
568
+ this.emitLive({ kind: 'event', data: { conversationId, actorId: person.actorId, type: 'message.created', data: stored } });
569
+ const timing = this.scenario.timing ?? {};
570
+ const assistantMessageId = this.nextId();
571
+ const emit = async (event) => {
572
+ await delay(timing.eventDelayMs ?? 40);
573
+ this.update(conversationId, current => applyNhAssistantEvent(current, event));
574
+ if (event.type === 'turn.completed' && event.data.status !== 'waiting-for-approval') {
575
+ this.activeActors.delete(conversationId);
576
+ this.emitChanged(conversationId);
577
+ }
578
+ this.emitLive({ kind: 'event', data: { conversationId, actorId: person.actorId, type: event.type, data: event.data } });
579
+ };
580
+ await delay(timing.firstEventDelayMs ?? 200);
581
+ await emit({ type: 'turn.started', data: { turnId: this.nextId(), userMessageId: userMessage.id, assistantMessageId } });
582
+ const turn = this.findTurn(text, conversation.agentId, null);
583
+ await this.playSteps(conversationId, assistantMessageId, turn?.steps ?? [], emit);
584
+ }
532
585
  /** Switches the simulated `NewHeap:AI:Assistant:Enabled` flag. */
533
586
  setEnabled(enabled) {
534
587
  this.enabledState.set(enabled);
@@ -546,8 +599,14 @@ class NhAssistantMockBackend {
546
599
  const segments = path.split('/').map(segment => decodeURIComponent(segment));
547
600
  if (method === 'GET' && path === 'status') {
548
601
  const limits = { ...defaultLimits, ...this.scenario.limits };
602
+ const collaboration = {
603
+ directory: (this.scenario.collaboration?.directory?.length ?? 0) > 0,
604
+ hubPath: NH_ASSISTANT_MOCK_HUB_PATH,
605
+ push: false,
606
+ maxParticipants: this.maxParticipants()
607
+ };
549
608
  return this.json(200, this.enabledState()
550
- ? { enabled: true, agents: this.admin.chatAgents(), limits, canAdminister: this.admin.administers }
609
+ ? { enabled: true, agents: this.admin.chatAgents(), limits, canAdminister: this.admin.administers, collaboration }
551
610
  : { enabled: false, agents: [], limits, canAdminister: false });
552
611
  }
553
612
  if (!this.enabledState()) {
@@ -559,6 +618,15 @@ class NhAssistantMockBackend {
559
618
  if (path === 'preferences') {
560
619
  return this.result(this.admin.handlePreferences(method, body));
561
620
  }
621
+ if (path === 'notifications') {
622
+ if (method === 'PUT') {
623
+ this.pushEnabled = body?.pushEnabled === true;
624
+ }
625
+ return this.json(200, { pushEnabled: this.pushEnabled, pushAvailable: false, publicKey: null });
626
+ }
627
+ if (path === 'notifications/push-subscription') {
628
+ return this.error(404, 'assistant-push-unavailable');
629
+ }
562
630
  if (segments[0] === 'admin') {
563
631
  return this.result(this.admin.handleAdmin(method, segments, body));
564
632
  }
@@ -580,15 +648,46 @@ class NhAssistantMockBackend {
580
648
  }
581
649
  if (segments.length === 2) {
582
650
  if (method === 'GET') {
583
- return this.json(200, conversation);
651
+ return this.json(200, this.view(conversation));
584
652
  }
585
653
  if (method === 'DELETE') {
586
654
  this.conversations.delete(conversation.id);
587
655
  this.pendingApprovals.delete(conversation.id);
656
+ this.emitLive({ kind: 'removed', data: { conversationId: conversation.id } });
588
657
  return this.empty(204);
589
658
  }
590
659
  return this.empty(405);
591
660
  }
661
+ if (method === 'POST' && segments.length === 3 && segments[2] === 'read') {
662
+ return this.markRead(conversation, body?.sequence);
663
+ }
664
+ if (segments.length === 3 && segments[2] === 'share-link') {
665
+ if (method === 'POST') {
666
+ const token = this.nextId().replace(/-/g, '');
667
+ this.shareTokens.set(conversation.id, token);
668
+ return this.json(200, { token });
669
+ }
670
+ if (method === 'DELETE') {
671
+ this.shareTokens.delete(conversation.id);
672
+ return this.empty(204);
673
+ }
674
+ }
675
+ if (method === 'POST' && segments.length === 3 && segments[2] === 'join') {
676
+ const token = body?.token;
677
+ return token && this.shareTokens.get(conversation.id) === token
678
+ ? this.json(200, this.view(conversation))
679
+ : this.error(404, 'assistant-share-link-invalid');
680
+ }
681
+ if (method === 'GET' && segments.length === 3 && segments[2] === 'participant-candidates') {
682
+ return this.candidates(conversation, query.get('query') ?? '');
683
+ }
684
+ if (method === 'POST' && segments.length === 3 && segments[2] === 'participants') {
685
+ return this.invite(conversation, body?.actorId);
686
+ }
687
+ if (method === 'DELETE' && segments.length === 4 && segments[2] === 'participants') {
688
+ const removed = this.removeMember(conversation.id, segments[3]);
689
+ return removed ? this.empty(204) : this.error(404, 'assistant-participant-not-found');
690
+ }
592
691
  if (method === 'POST' && segments.length === 3 && segments[2] === 'messages') {
593
692
  return this.sendMessage(conversation, body);
594
693
  }
@@ -605,7 +704,7 @@ class NhAssistantMockBackend {
605
704
  const itemsPerPage = Math.max(1, Number(query.get('itemsPerPage') ?? 20));
606
705
  const all = [...this.conversations.values()]
607
706
  .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
608
- .map(({ id, agentId, title, status, createdAt, updatedAt }) => ({ id, agentId, title, status, createdAt, updatedAt }));
707
+ .map(conversation => this.summary(conversation));
609
708
  return { items: all.slice((page - 1) * itemsPerPage, page * itemsPerPage), total: all.length };
610
709
  }
611
710
  create(request) {
@@ -626,7 +725,8 @@ class NhAssistantMockBackend {
626
725
  pendingApproval: null
627
726
  };
628
727
  this.conversations.set(conversation.id, conversation);
629
- return this.json(201, conversation);
728
+ this.readPositions.set(conversation.id, 0);
729
+ return this.json(201, this.view(conversation));
630
730
  }
631
731
  sendMessage(conversation, request) {
632
732
  const text = request?.text?.trim() ?? '';
@@ -643,12 +743,18 @@ class NhAssistantMockBackend {
643
743
  const turn = this.findTurn(text, conversation.agentId, pageContext);
644
744
  const userMessageId = this.nextId();
645
745
  const assistantMessageId = this.nextId();
746
+ this.activeActors.set(conversation.id, this.currentUser.actorId);
646
747
  this.update(conversation.id, current => ({
647
748
  ...current,
648
749
  title: current.title ?? text.slice(0, 60),
649
750
  status: 'running',
650
- messages: [...current.messages, { id: userMessageId, role: 'user', createdAt: new Date().toISOString(), parts: [{ type: 'text', text }] }]
751
+ messages: [
752
+ ...current.messages,
753
+ { id: userMessageId, role: 'user', createdAt: new Date().toISOString(), parts: [{ type: 'text', text }], authorActorId: this.currentUser.actorId }
754
+ ]
651
755
  }));
756
+ // The sender has read their own message.
757
+ this.readPositions.set(conversation.id, lastSequence(this.conversations.get(conversation.id)));
652
758
  return this.stream(conversation.id, async (emit) => {
653
759
  await emit({ type: 'turn.started', data: { turnId: this.nextId(), userMessageId, assistantMessageId } });
654
760
  await this.playSteps(conversation.id, assistantMessageId, turn?.steps ?? [], emit);
@@ -659,6 +765,10 @@ class NhAssistantMockBackend {
659
765
  if (!pending || pending.approval.approvalId !== approvalId) {
660
766
  return this.empty(404);
661
767
  }
768
+ const decider = this.activeActors.get(conversation.id);
769
+ if (decider && decider !== this.currentUser.actorId) {
770
+ return this.error(403, 'assistant-approval-forbidden');
771
+ }
662
772
  if (request?.expectedProposalHash !== pending.approval.proposalHash) {
663
773
  return this.empty(409);
664
774
  }
@@ -857,10 +967,133 @@ class NhAssistantMockBackend {
857
967
  }
858
968
  update(conversationId, change) {
859
969
  const conversation = this.conversations.get(conversationId);
860
- if (conversation) {
861
- this.conversations.set(conversationId, { ...change(conversation), updatedAt: new Date().toISOString() });
970
+ if (!conversation) {
971
+ return;
972
+ }
973
+ const updated = withSequences({ ...change(conversation), updatedAt: new Date().toISOString() });
974
+ if (updated.status !== 'running' && updated.status !== 'waiting-for-approval') {
975
+ this.activeActors.delete(conversationId);
976
+ }
977
+ this.conversations.set(conversationId, updated);
978
+ if (updated.status !== conversation.status || updated.title !== conversation.title) {
979
+ this.emitChanged(conversationId);
862
980
  }
863
981
  }
982
+ /** The conversation as the signed-in user sees it. */
983
+ view(conversation) {
984
+ const members = this.members.get(conversation.id) ?? [];
985
+ return {
986
+ ...conversation,
987
+ ...this.summary(conversation),
988
+ members,
989
+ shareToken: this.shareTokens.get(conversation.id) ?? null,
990
+ currentActorId: this.currentUser.actorId
991
+ };
992
+ }
993
+ summary(conversation) {
994
+ const latest = lastSequence(conversation);
995
+ const members = this.members.get(conversation.id) ?? [];
996
+ return {
997
+ id: conversation.id,
998
+ agentId: conversation.agentId,
999
+ title: conversation.title,
1000
+ status: conversation.status,
1001
+ createdAt: conversation.createdAt,
1002
+ updatedAt: conversation.updatedAt,
1003
+ role: 'owner',
1004
+ participantCount: Math.max(0, members.length - 1),
1005
+ lastMessageSequence: latest,
1006
+ lastReadSequence: Math.min(this.readPositions.get(conversation.id) ?? latest, latest),
1007
+ activeActorId: this.activeActors.get(conversation.id) ?? null
1008
+ };
1009
+ }
1010
+ markRead(conversation, sequence) {
1011
+ const latest = lastSequence(conversation);
1012
+ const target = Math.min(sequence ?? latest, latest);
1013
+ if (target > (this.readPositions.get(conversation.id) ?? 0)) {
1014
+ this.readPositions.set(conversation.id, target);
1015
+ this.emitLive({ kind: 'read', data: { conversationId: conversation.id, lastReadSequence: target } });
1016
+ }
1017
+ return this.empty(204);
1018
+ }
1019
+ candidates(conversation, query) {
1020
+ const directory = this.scenario.collaboration?.directory ?? [];
1021
+ if (directory.length === 0) {
1022
+ return this.error(404, 'assistant-directory-unavailable');
1023
+ }
1024
+ const text = query.trim().toLowerCase();
1025
+ if (text.length < 2) {
1026
+ return this.error(400, 'assistant-validation');
1027
+ }
1028
+ const members = new Set((this.members.get(conversation.id) ?? []).map(member => member.actorId));
1029
+ return this.json(200, directory
1030
+ .filter(person => person.actorId !== this.currentUser.actorId && !members.has(person.actorId))
1031
+ .filter(person => person.displayName.toLowerCase().includes(text))
1032
+ .map(person => ({ actorId: person.actorId, displayName: person.displayName, detail: person.detail ?? null })));
1033
+ }
1034
+ invite(conversation, actorId) {
1035
+ const person = (this.scenario.collaboration?.directory ?? []).find(item => item.actorId === actorId);
1036
+ if (!person) {
1037
+ return this.error(404, 'assistant-participant-not-found');
1038
+ }
1039
+ const participants = (this.members.get(conversation.id) ?? []).filter(member => member.role === 'participant');
1040
+ if (participants.length >= this.maxParticipants()) {
1041
+ return this.error(409, 'assistant-participant-limit-reached');
1042
+ }
1043
+ this.addMember(conversation.id, person, 'participant');
1044
+ return this.empty(204);
1045
+ }
1046
+ addMember(conversationId, person, role) {
1047
+ const members = this.members.get(conversationId) ?? [];
1048
+ if (members.some(member => member.actorId === person.actorId)) {
1049
+ return;
1050
+ }
1051
+ const joinedAt = new Date().toISOString();
1052
+ const owner = members.length === 0
1053
+ ? [{ actorId: this.currentUser.actorId, displayName: this.currentUser.displayName, role: 'owner', joinedAt }]
1054
+ : [];
1055
+ this.members.set(conversationId, [...members, ...owner, { actorId: person.actorId, displayName: person.displayName, role, joinedAt }]);
1056
+ this.emitChanged(conversationId);
1057
+ }
1058
+ removeMember(conversationId, actorId) {
1059
+ const members = this.members.get(conversationId) ?? [];
1060
+ if (!members.some(member => member.actorId === actorId && member.role === 'participant')) {
1061
+ return false;
1062
+ }
1063
+ const remaining = members.filter(member => member.actorId !== actorId);
1064
+ this.members.set(conversationId, remaining.length > 1 ? remaining : []);
1065
+ this.emitChanged(conversationId);
1066
+ return true;
1067
+ }
1068
+ emitChanged(conversationId) {
1069
+ const conversation = this.conversations.get(conversationId);
1070
+ if (!conversation) {
1071
+ return;
1072
+ }
1073
+ const summary = this.summary(conversation);
1074
+ this.emitLive({
1075
+ kind: 'changed',
1076
+ data: {
1077
+ conversationId,
1078
+ status: summary.status,
1079
+ title: summary.title,
1080
+ updatedAt: summary.updatedAt,
1081
+ activeActorId: summary.activeActorId ?? null,
1082
+ lastMessageSequence: summary.lastMessageSequence ?? 0,
1083
+ participantCount: summary.participantCount ?? 0
1084
+ }
1085
+ });
1086
+ }
1087
+ emitLive(message) {
1088
+ // Delivered after the current request, as a hub message would arrive.
1089
+ queueMicrotask(() => this.liveSubject.next(message));
1090
+ }
1091
+ maxParticipants() {
1092
+ return this.scenario.collaboration?.maxParticipants ?? 20;
1093
+ }
1094
+ error(status, code) {
1095
+ return this.json(status, { code, messageKey: `nh-assistant.errors.${code}` });
1096
+ }
864
1097
  nextId() {
865
1098
  this.sequence++;
866
1099
  const suffix = this.sequence.toString(16).padStart(12, '0');
@@ -875,28 +1108,100 @@ class NhAssistantMockBackend {
875
1108
  empty(status) {
876
1109
  return new Response(null, { status });
877
1110
  }
878
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantMockBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
879
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantMockBackend }); }
1111
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1112
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockBackend }); }
880
1113
  }
881
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantMockBackend, decorators: [{
1114
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockBackend, decorators: [{
882
1115
  type: Injectable
883
1116
  }], ctorParameters: () => [] });
884
1117
  function delay(milliseconds) {
885
1118
  return new Promise(resolve => setTimeout(resolve, milliseconds));
886
1119
  }
1120
+ /** Numbers messages in order, as the server does, keeping numbers that are already set. */
1121
+ function withSequences(conversation) {
1122
+ let next = lastSequence(conversation);
1123
+ if (conversation.messages.every(message => message.sequence !== undefined)) {
1124
+ return conversation;
1125
+ }
1126
+ return {
1127
+ ...conversation,
1128
+ messages: conversation.messages.map(message => message.sequence === undefined ? { ...message, sequence: ++next } : message)
1129
+ };
1130
+ }
1131
+ function lastSequence(conversation) {
1132
+ return conversation.messages.reduce((maximum, message) => Math.max(maximum, message.sequence ?? 0), 0);
1133
+ }
1134
+
1135
+ /**
1136
+ * Stands in for the SignalR live service: it delivers the mock back-end's live updates in
1137
+ * memory, so shared conversations, other tabs and parallel turns behave as with a real hub.
1138
+ */
1139
+ class NhAssistantMockLiveService {
1140
+ constructor() {
1141
+ this.backend = inject(NhAssistantMockBackend);
1142
+ this.changedSubject = new Subject();
1143
+ this.readSubject = new Subject();
1144
+ this.removedSubject = new Subject();
1145
+ this.eventSubject = new Subject();
1146
+ this.resyncSubject = new Subject();
1147
+ this.stateSignal = signal('off', ...(ngDevMode ? [{ debugName: "stateSignal" }] : []));
1148
+ this.changed$ = this.changedSubject.asObservable();
1149
+ this.read$ = this.readSubject.asObservable();
1150
+ this.removed$ = this.removedSubject.asObservable();
1151
+ this.events$ = this.eventSubject.asObservable();
1152
+ this.resync$ = this.resyncSubject.asObservable();
1153
+ this.state = this.stateSignal.asReadonly();
1154
+ inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
1155
+ }
1156
+ async connect(hubPath) {
1157
+ await this.stop();
1158
+ if (!hubPath) {
1159
+ return;
1160
+ }
1161
+ this.subscription = this.backend.live$.subscribe(message => {
1162
+ switch (message.kind) {
1163
+ case 'changed':
1164
+ this.changedSubject.next(message.data);
1165
+ break;
1166
+ case 'read':
1167
+ this.readSubject.next(message.data);
1168
+ break;
1169
+ case 'removed':
1170
+ this.removedSubject.next(message.data);
1171
+ break;
1172
+ case 'event':
1173
+ this.eventSubject.next(message.data);
1174
+ break;
1175
+ }
1176
+ });
1177
+ this.stateSignal.set('connected');
1178
+ this.resyncSubject.next();
1179
+ }
1180
+ async stop() {
1181
+ this.subscription?.unsubscribe();
1182
+ this.subscription = undefined;
1183
+ this.stateSignal.set('off');
1184
+ }
1185
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockLiveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1186
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockLiveService }); }
1187
+ }
1188
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockLiveService, decorators: [{
1189
+ type: Injectable
1190
+ }], ctorParameters: () => [] });
887
1191
 
888
1192
  /**
889
1193
  * Replaces the assistant transport with an in-memory back-end that plays `script`.
890
1194
  * Register it after `provideNhAssistant(...)` in the same injector. Every client feature
891
- * (status, agents, conversations, streamed turns, approvals, cancellation) then works
892
- * without a server. Inject `NhAssistantMockBackend` to switch the feature flag or to
893
- * inspect the received requests.
1195
+ * (status, agents, conversations, streamed turns, approvals, cancellation, sharing, read
1196
+ * state and live updates) then works without a server. Inject `NhAssistantMockBackend` to
1197
+ * switch the feature flag, simulate a colleague or inspect the received requests.
894
1198
  */
895
1199
  function provideNhAssistantMockApi(script) {
896
1200
  return makeEnvironmentProviders([
897
1201
  { provide: NH_ASSISTANT_MOCK_SCENARIO, useValue: script },
898
1202
  NhAssistantMockBackend,
899
- { provide: NH_ASSISTANT_FETCH, useFactory: () => inject(NhAssistantMockBackend).fetch }
1203
+ { provide: NH_ASSISTANT_FETCH, useFactory: () => inject(NhAssistantMockBackend).fetch },
1204
+ { provide: NhAssistantLiveService, useClass: NhAssistantMockLiveService }
900
1205
  ]);
901
1206
  }
902
1207
 
@@ -908,5 +1213,5 @@ function provideNhAssistantMockApi(script) {
908
1213
  * Generated bundle index. Do not edit.
909
1214
  */
910
1215
 
911
- export { NH_ASSISTANT_MOCK_SCENARIO, NhAssistantMockBackend, provideNhAssistantMockApi };
1216
+ export { NH_ASSISTANT_MOCK_HUB_PATH, NH_ASSISTANT_MOCK_SCENARIO, NhAssistantMockBackend, NhAssistantMockLiveService, provideNhAssistantMockApi };
912
1217
  //# sourceMappingURL=newheap-platform-ai-chat-testing.mjs.map