@newheap/platform-ai-chat 0.8.1 → 0.10.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,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, DestroyRef, Injectable, EnvironmentInjector, runInInjectionContext, PLATFORM_ID, signal, computed, effect, untracked, Injector, afterNextRender, makeEnvironmentProviders, provideEnvironmentInitializer, input, ChangeDetectionStrategy, Component, ChangeDetectorRef, Pipe, output, ElementRef, viewChild, HostListener, ViewContainerRef } from '@angular/core';
2
+ import { InjectionToken, inject, DestroyRef, Injectable, EnvironmentInjector, runInInjectionContext, PLATFORM_ID, signal, computed, effect, untracked, Injector, afterNextRender, makeEnvironmentProviders, provideEnvironmentInitializer, input, ChangeDetectionStrategy, Component, ChangeDetectorRef, Pipe, output, ElementRef, viewChild, HostListener, linkedSignal, ViewContainerRef } from '@angular/core';
3
3
  import { TranslateService, TranslatePipe } from '@ngx-translate/core';
4
- import { merge, Observable, Subject, firstValueFrom, Subscription, isObservable } from 'rxjs';
4
+ import { merge, Observable, Subject, firstValueFrom, Subscription, isObservable, filter, debounceTime, BehaviorSubject, map, forkJoin } from 'rxjs';
5
5
  import { isPlatformBrowser, DOCUMENT, DatePipe, NgTemplateOutlet } from '@angular/common';
6
6
  import { BreakpointObserver } from '@angular/cdk/layout';
7
7
  import { Overlay } from '@angular/cdk/overlay';
@@ -48,12 +48,34 @@ const NhAssistantClientErrorCodes = {
48
48
  conversationRemoved: 'assistant-conversation-removed'
49
49
  };
50
50
 
51
- /*
52
- * Wire models of the assistant preference and administration endpoints.
53
- *
54
- * Literal translation of the assistant API contract (preferences and `admin/*`). Keep the
55
- * field names identical to the JSON the server returns.
56
- */
51
+ /** The most proposals one `POST approvals/lookup` reads. */
52
+ const NH_ASSISTANT_MAX_PROPOSAL_LOOKUP = 100;
53
+ /** Maximum number of subjects one `POST subjects/{type}/summary` request accepts. */
54
+ const NH_ASSISTANT_MAX_SUBJECT_SUMMARY = 200;
55
+ /** The feedback categories in the order people choose them. */
56
+ const NH_ASSISTANT_FEEDBACK_CATEGORIES = [
57
+ 'incorrect',
58
+ 'wrong-value',
59
+ 'not-needed',
60
+ 'not-now',
61
+ 'already-done',
62
+ 'not-allowed',
63
+ 'other'
64
+ ];
65
+ /** The group of each feedback category, as the server reports it. */
66
+ const NH_ASSISTANT_FEEDBACK_GROUPS = {
67
+ incorrect: 'wrong',
68
+ 'wrong-value': 'wrong',
69
+ 'not-needed': 'wrong',
70
+ 'not-now': 'right-but-not-now',
71
+ 'already-done': 'right-but-not-now',
72
+ 'not-allowed': 'not-allowed',
73
+ other: 'other'
74
+ };
75
+ /** Maximum length of the text of a feedback. */
76
+ const NH_ASSISTANT_MAX_FEEDBACK_TEXT = 1_000;
77
+ /** Maximum number of proposals one `POST approvals/feedback` request accepts. */
78
+ const NH_ASSISTANT_MAX_BULK_FEEDBACK = 50;
57
79
 
58
80
  const NH_ASSISTANT_CONFIG = new InjectionToken('NH_ASSISTANT_CONFIG');
59
81
  const NH_ASSISTANT_ACCESS_POLICY = new InjectionToken('NH_ASSISTANT_ACCESS_POLICY');
@@ -83,6 +105,11 @@ var en = {
83
105
  unread: "{{count}} unread conversations",
84
106
  running: "{{count}} conversations working"
85
107
  },
108
+ "subject-badge": {
109
+ "to-decide": "Proposals to decide: {{toDecide}} of {{open}} open.",
110
+ open: "Open proposals: {{open}}.",
111
+ working: "An agent is working on this."
112
+ },
86
113
  panel: {
87
114
  title: "Assistant",
88
115
  "region-label": "Assistant panel",
@@ -138,7 +165,9 @@ var en = {
138
165
  working: "The assistant is working",
139
166
  loading: "Loading conversation",
140
167
  elapsed: "{{seconds}}s",
141
- participant: "Participant"
168
+ participant: "Participant",
169
+ trigger: "Event",
170
+ rule: "Automatic rule"
142
171
  },
143
172
  composer: {
144
173
  label: "Message",
@@ -185,8 +214,16 @@ var en = {
185
214
  succeeded: "Completed",
186
215
  failed: "Failed",
187
216
  "awaiting-approval": "Awaiting approval",
188
- rejected: "Rejected"
189
- }
217
+ rejected: "Rejected",
218
+ "not-executed": "Not run"
219
+ },
220
+ "grant-source": {
221
+ delegated: "On behalf of the initiator",
222
+ agent: "Agent rights",
223
+ "delegated-hint": "The agent used the rights of the person who started it.",
224
+ "agent-hint": "The agent used a right it was granted itself, beyond the rights of the people involved."
225
+ },
226
+ restricted: "Shielded: the agent obtained this with its own rights, which you do not have."
190
227
  },
191
228
  "tool-group": {
192
229
  running: "Calling {{name}}…",
@@ -195,6 +232,7 @@ var en = {
195
232
  "used-many": "Used {{count}} tools",
196
233
  progress: "{{finished}} of {{total}} done",
197
234
  failed: "{{count}} failed",
235
+ "not-executed": "{{count}} not run",
198
236
  "show-calls": "Show tool calls",
199
237
  "hide-calls": "Hide tool calls"
200
238
  },
@@ -216,10 +254,65 @@ var en = {
216
254
  pending: "Pending",
217
255
  approved: "Approved",
218
256
  rejected: "Rejected",
219
- expired: "Expired"
257
+ expired: "Expired",
258
+ withdrawn: "Withdrawn",
259
+ stale: "Outdated",
260
+ executing: "Running",
261
+ executed: "Done",
262
+ "execution-failed": "Failed",
263
+ "execution-unknown": "Outcome unknown",
264
+ resolved: "Resolved",
265
+ shadow: "Shadow"
220
266
  },
221
267
  "waiting-for": "Waiting for {{name}} to decide. The action uses their permissions.",
222
- "waiting-for-participant": "Waiting for the participant who started this task to decide."
268
+ "waiting-for-participant": "Waiting for the participant who started this task to decide.",
269
+ "waiting-for-approver": "Waiting for an approver to decide.",
270
+ "waiting-for-other-approver": "You started this work, so another approver decides.",
271
+ "give-feedback": "What was wrong?",
272
+ "stale-kind": {
273
+ "already-done": "Already done",
274
+ changed: "Changed",
275
+ "not-applicable": "No longer needed"
276
+ },
277
+ rationale: "Why: {{rationale}}",
278
+ superseded: "Replaced by a newer proposal"
279
+ },
280
+ "proposal-feedback": {
281
+ legend: "What was wrong? (optional)",
282
+ bulk: "Applies to the {{count}} proposals you rejected.",
283
+ categories: {
284
+ incorrect: "Incorrect",
285
+ "wrong-value": "Wrong value",
286
+ "not-needed": "Not needed",
287
+ "not-now": "Not now",
288
+ "already-done": "Already done",
289
+ "not-allowed": "Not allowed",
290
+ other: "Other"
291
+ },
292
+ hints: {
293
+ incorrect: "The proposal is factually wrong.",
294
+ "wrong-value": "The right action, with the wrong values.",
295
+ "not-needed": "The change is unnecessary.",
296
+ "not-now": "Right, but not at this moment.",
297
+ "already-done": "Already done or in progress elsewhere.",
298
+ "not-allowed": "Against policy, or beyond what the agent should do.",
299
+ other: "Explain it in the text below."
300
+ },
301
+ groups: {
302
+ wrong: "The agent was wrong",
303
+ "right-but-not-now": "Right, but not now",
304
+ "not-allowed": "Not allowed",
305
+ other: "Other"
306
+ },
307
+ "text-label": "Anything to add? (optional)",
308
+ "text-hint": "The agent's administrators read this to improve it.",
309
+ characters: "{{count}}/{{max}}",
310
+ send: "Send",
311
+ sending: "Sending…",
312
+ skip: "Skip",
313
+ cancel: "Cancel",
314
+ thanks: "Thanks for your feedback",
315
+ partial: "Saved for {{stored}} of {{total}} proposals; the others changed in the meantime."
223
316
  },
224
317
  "result-codes": {
225
318
  "api-bridge-validation": "The request was not valid.",
@@ -228,7 +321,15 @@ var en = {
228
321
  "api-bridge-not-found": "The item was not found.",
229
322
  "api-bridge-conflict": "The item was changed in the meantime.",
230
323
  "api-bridge-upstream": "The service is not available.",
231
- "api-bridge-timeout": "The request took too long."
324
+ "api-bridge-timeout": "The request took too long.",
325
+ "assistant-proposal-recorded": "Proposed; waits for a person's approval",
326
+ "assistant-proposal-rejected": "Not run: the proposal was rejected.",
327
+ "assistant-proposal-expired": "Not run: nobody decided before the proposal expired.",
328
+ "assistant-proposal-withdrawn": "Not run: the proposal was withdrawn.",
329
+ "assistant-proposal-stale": "Not run: the proposal no longer applied.",
330
+ "assistant-proposal-execution-failed": "The approved proposal could not be run.",
331
+ "assistant-proposal-already-open": "Not proposed again: the same proposal already waits.",
332
+ "assistant-proposal-recently-rejected": "Not proposed again: people rejected it recently."
232
333
  },
233
334
  errors: {
234
335
  generic: "Something went wrong. Please try again.",
@@ -287,7 +388,43 @@ var en = {
287
388
  "assistant-directory-unavailable": "Inviting colleagues directly is not available.",
288
389
  "assistant-push-unavailable": "Desktop notifications are not available.",
289
390
  "assistant-push-subscription-invalid": "This browser could not be registered for notifications.",
290
- "assistant-conversation-removed": "This conversation is no longer available to you."
391
+ "assistant-conversation-removed": "This conversation is no longer available to you.",
392
+ "assistant-subject-not-found": "The subject no longer exists.",
393
+ "assistant-subject-type-unknown": "This kind of subject is not known to the assistant.",
394
+ "assistant-subject-access-denied": "Your role on this subject does not allow this.",
395
+ "assistant-no-accountable-owner": "Nobody answers for this agent on this subject.",
396
+ "assistant-agent-disabled": "This agent has been stopped.",
397
+ "assistant-agent-not-configured": "This agent cannot run on its own.",
398
+ "assistant-run-not-found": "The run was not found.",
399
+ "assistant-run-not-retryable": "This run cannot be retried.",
400
+ "assistant-approval-policy-denied": "You may not decide this proposal.",
401
+ "assistant-four-eyes-required": "Someone other than the person who started this must decide it.",
402
+ "assistant-execution-not-uncertain": "This execution is not waiting to be resolved.",
403
+ "assistant-proposal-stale": "This proposal no longer applies, for example because the work was already done. Nothing was changed.",
404
+ "assistant-proposal-check-failed": "Whether this proposal still applies could not be checked, so nothing was changed. Try again later.",
405
+ "assistant-proposal-already-open": "The same proposal already waits for a decision.",
406
+ "assistant-proposal-recently-rejected": "People rejected the same proposal recently.",
407
+ "assistant-proposal-superseded": "A newer proposal replaced this one.",
408
+ "assistant-proposal-refused": "This action cannot be proposed by an automatic rule.",
409
+ "assistant-trigger-not-found": "The trigger was not found.",
410
+ "assistant-webhook-signature-invalid": "The webhook call has no valid signature.",
411
+ "assistant-webhook-payload-invalid": "The webhook call could not be read.",
412
+ "assistant-memory-not-configured": "This agent keeps no working memory.",
413
+ "assistant-memory-invalid": "The working memory change does not fit its schema.",
414
+ "assistant-memory-conflict": "The working memory changed at the same time.",
415
+ "assistant-artifact-invalid": "The file is not valid or too large.",
416
+ "assistant-artifact-outside-turn": "Files can only be saved while the agent works.",
417
+ "assistant-artifact-not-found": "The file was not found.",
418
+ "assistant-feedback-invalid": "Choose one of the listed reasons and keep the explanation under 1,000 characters.",
419
+ "assistant-feedback-not-accepted": "Feedback is possible only after a proposal was rejected or closed.",
420
+ "assistant-consult-subject-unavailable": "The subject does not exist or you may not consult the agent about it.",
421
+ "assistant-consult-agent-unavailable": "The agent cannot be consulted right now.",
422
+ "assistant-consult-depth-exceeded": "This consultation would ask agents in a loop or nest too deeply.",
423
+ "assistant-consult-budget-exhausted": "The agent's daily budget is spent.",
424
+ "assistant-consult-limit-reached": "You reached the daily number of consultations.",
425
+ "assistant-consult-timeout": "The agent did not answer in time.",
426
+ "assistant-consult-failed": "The agent could not answer.",
427
+ "assistant-consult-read-only": "A consultation cannot change anything."
291
428
  },
292
429
  notices: {
293
430
  "no-answer": "The assistant finished without an answer. Try again or rephrase your question.",
@@ -401,7 +538,8 @@ var en = {
401
538
  propose: "Propose",
402
539
  simulate: "Simulate",
403
540
  execute: "Execute"
404
- }
541
+ },
542
+ feedback: "Feedback"
405
543
  },
406
544
  "agent-editor": {
407
545
  "new-title": "New agent",
@@ -515,6 +653,59 @@ var en = {
515
653
  "missing-warning": "The server no longer lists this tool.",
516
654
  save: "Save tool",
517
655
  back: "Back to the servers"
656
+ },
657
+ feedback: {
658
+ title: "Feedback on proposals",
659
+ description: "What people said when they rejected this agent's proposals. Compare agent versions to see whether a change to the prompt, tools or policy helped.",
660
+ agent: "{{name}} ({{id}})",
661
+ back: "Back to agents",
662
+ refresh: "Refresh",
663
+ "export": "Export (JSON Lines)",
664
+ exporting: "Exporting…",
665
+ "export-hint": "The export holds subject data and the proposed tool input, and is recorded in the audit log.",
666
+ period: "Period",
667
+ periods: {
668
+ "last-7-days": "Last 7 days",
669
+ "last-30-days": "Last 30 days",
670
+ "last-90-days": "Last 90 days"
671
+ },
672
+ tool: "Tool",
673
+ "all-tools": "All tools",
674
+ category: "Category",
675
+ "all-categories": "All categories",
676
+ "category-hint": "The category narrows the feedback and the export.",
677
+ "summary-title": "Per tool and agent version",
678
+ "summary-empty": "The agent made no proposals in this period.",
679
+ columns: {
680
+ tool: "Tool",
681
+ version: "Agent version",
682
+ approved: "Approved",
683
+ rejected: "Rejected",
684
+ expired: "Expired",
685
+ "rejection-rate": "Rejection rate",
686
+ "with-feedback": "Rejected with feedback",
687
+ invalidated: "No longer needed",
688
+ people: "People",
689
+ categories: "Top categories"
690
+ },
691
+ version: "v{{version}}",
692
+ consultations: "Consultations",
693
+ "no-decisions": "Not decided yet",
694
+ "no-categories": "None",
695
+ "invalidated-hint": "Open proposals that closed before anyone decided, because the work was already done or no longer applied. They do not count as rejected.",
696
+ "list-title": "Feedback",
697
+ "list-count": "{{count}} in total",
698
+ "list-empty": "Nobody gave feedback in this period.",
699
+ "list-empty-filtered": "No feedback matches these filters.",
700
+ "no-text": "No explanation given.",
701
+ proposal: "Proposal:",
702
+ author: "By {{actor}}",
703
+ bulk: "Bulk",
704
+ "bulk-hint": "Given once for several proposals rejected together; weigh it lower.",
705
+ previous: "Previous",
706
+ next: "Next",
707
+ page: "Page {{page}} of {{pages}}",
708
+ pages: "Pages"
518
709
  }
519
710
  },
520
711
  "page-context": {
@@ -561,6 +752,159 @@ var en = {
561
752
  remove: "Remove {{name}}",
562
753
  leave: "Leave conversation",
563
754
  "no-conversation": "Start or open a conversation to share it."
755
+ },
756
+ "agent-runs": {
757
+ loading: "Loading…",
758
+ "load-failed": "This could not be loaded.",
759
+ retry: "Try again",
760
+ refresh: "Refresh",
761
+ role: {
762
+ none: "No role on this subject",
763
+ reader: "Reader",
764
+ participant: "Participant",
765
+ approver: "Approver"
766
+ },
767
+ thread: {
768
+ title: "Agent",
769
+ "waiting-inputs": "{{count}} messages wait for the next run",
770
+ "open-conversation": "Open conversation",
771
+ "start-message": "Instruction for the agent (optional)",
772
+ start: "Start the agent",
773
+ starting: "Starting…",
774
+ "no-proposals": "The agent has proposed nothing here yet.",
775
+ "no-thread": "The agent has not worked on this yet.",
776
+ tabs: {
777
+ proposals: "Proposals",
778
+ runs: "Runs",
779
+ consultations: "Consultations",
780
+ memory: "Working memory",
781
+ artifacts: "Files"
782
+ },
783
+ outcome: {
784
+ started: "The agent starts shortly.",
785
+ queued: "The agent starts after the current run.",
786
+ coalesced: "Your request joins the run that is about to start.",
787
+ superseded: "The current run stops and the agent starts again.",
788
+ ignored: "The agent is busy; nothing was started.",
789
+ deduplicated: "This was already started.",
790
+ rejected: "The agent could not be started."
791
+ }
792
+ },
793
+ subject: {
794
+ title: "Agent suggestions",
795
+ description: "Proposals that agents made here and that wait for a decision, the first to expire on top.",
796
+ empty: "No open suggestions from agents.",
797
+ unavailable: "You cannot see agent work on this item.",
798
+ agents: "Agents",
799
+ idle: "Not working now",
800
+ opening: "Opening…",
801
+ "open-conversation-label": "Open conversation with {{agent}}",
802
+ "start-label": "Start the agent: {{agent}}",
803
+ "assistant-unavailable": "The assistant is not available."
804
+ },
805
+ inbox: {
806
+ title: "Proposals to decide",
807
+ description: "Proposals of agents that wait for your decision, the ones that expire first on top.",
808
+ empty: "No proposals wait for you.",
809
+ select: "Select this proposal",
810
+ "select-all": "Select all",
811
+ "approve-selected": "Approve selected",
812
+ "reject-selected": "Reject selected",
813
+ previous: "Previous",
814
+ next: "Next",
815
+ page: "Page {{page}} of {{pages}}",
816
+ pages: "Pages"
817
+ },
818
+ proposal: {
819
+ approve: "Approve",
820
+ reject: "Reject",
821
+ expires: "Expires {{date}}",
822
+ "decided-by": "Decided by {{actor}}",
823
+ "arguments": "Input",
824
+ "re-execute": "Run again",
825
+ "mark-resolved": "Mark as resolved",
826
+ "unknown-hint": "The execution stopped without a known outcome. Check whether it happened before you run it again.",
827
+ mode: {
828
+ inline: "The run waits",
829
+ deferred: "Decide later"
830
+ },
831
+ status: {
832
+ pending: "Waiting",
833
+ approved: "Approved",
834
+ rejected: "Rejected",
835
+ expired: "Expired",
836
+ withdrawn: "Withdrawn",
837
+ stale: "Outdated",
838
+ executing: "Running",
839
+ executed: "Done",
840
+ "execution-failed": "Failed",
841
+ "execution-unknown": "Outcome unknown",
842
+ resolved: "Resolved",
843
+ shadow: "Shadow"
844
+ },
845
+ "from-consultation": "From a consultation",
846
+ "from-consultation-hint": "Another assistant consulted the agent for this person; it runs with that person's rights only.",
847
+ "give-feedback": "Give feedback",
848
+ "change-feedback": "Change feedback",
849
+ "your-feedback": "Your feedback: {{category}}",
850
+ "rejected-one": "You rejected: {{summary}}",
851
+ closed: "Closed in the meantime",
852
+ dismiss: "Dismiss",
853
+ "dismiss-label": "Dismiss the closed proposal: {{summary}}",
854
+ "show-successor": "Show the newer proposal",
855
+ "stale-by": "Overtaken by {{actor}}"
856
+ },
857
+ run: {
858
+ empty: "No runs yet.",
859
+ status: {
860
+ queued: "Queued",
861
+ running: "Running",
862
+ "waiting-for-approval": "Waiting for approval",
863
+ completed: "Completed",
864
+ failed: "Failed",
865
+ cancelled: "Cancelled"
866
+ },
867
+ shadow: "Shadow",
868
+ kind: {
869
+ rule: "Automatic rule",
870
+ "rule-then-model": "Rule, then the model"
871
+ },
872
+ trigger: "Trigger: {{trigger}}",
873
+ "tool-calls": "{{count}} tool calls",
874
+ tokens: "{{count}} tokens",
875
+ proposals: "{{count}} proposals",
876
+ attempt: "Attempt {{attempt}}",
877
+ cancel: "Cancel",
878
+ resume: "Resume",
879
+ restart: "Start again"
880
+ },
881
+ memory: {
882
+ "not-configured": "This agent keeps no working memory.",
883
+ version: "Schema version {{version}}",
884
+ updated: "Updated {{date}}",
885
+ empty: "The agent has not noted anything yet."
886
+ },
887
+ artifacts: {
888
+ "download-failed": "The file could not be downloaded.",
889
+ empty: "No files yet.",
890
+ version: "v{{version}}",
891
+ restricted: "Shielded (agent rights)",
892
+ size: "{{size}} bytes",
893
+ download: "Download"
894
+ },
895
+ consultation: {
896
+ empty: "No assistant has consulted this agent about this subject yet.",
897
+ "private": "Only who asked and how it ended is shown here; the question and the answer stay in the asking conversation.",
898
+ "asked-by": "Asked by {{name}}",
899
+ through: "through {{agent}}",
900
+ depth: "Nested, level {{depth}}",
901
+ more: "{{count}} older consultations are not shown.",
902
+ status: {
903
+ running: "Running",
904
+ completed: "Answered",
905
+ failed: "Not answered"
906
+ }
907
+ }
564
908
  }
565
909
  }
566
910
  };
@@ -574,6 +918,11 @@ var nl = {
574
918
  unread: "{{count}} ongelezen gesprekken",
575
919
  running: "{{count}} gesprekken bezig"
576
920
  },
921
+ "subject-badge": {
922
+ "to-decide": "Voorstellen om te beslissen: {{toDecide}} van {{open}} open.",
923
+ open: "Openstaande voorstellen: {{open}}.",
924
+ working: "Een agent werkt hieraan."
925
+ },
577
926
  panel: {
578
927
  title: "Assistent",
579
928
  "region-label": "Assistentpaneel",
@@ -629,7 +978,9 @@ var nl = {
629
978
  working: "De assistent is bezig",
630
979
  loading: "Gesprek laden",
631
980
  elapsed: "{{seconds}} s",
632
- participant: "Deelnemer"
981
+ participant: "Deelnemer",
982
+ trigger: "Gebeurtenis",
983
+ rule: "Automatische regel"
633
984
  },
634
985
  composer: {
635
986
  label: "Bericht",
@@ -676,8 +1027,16 @@ var nl = {
676
1027
  succeeded: "Voltooid",
677
1028
  failed: "Mislukt",
678
1029
  "awaiting-approval": "Wacht op goedkeuring",
679
- rejected: "Afgewezen"
680
- }
1030
+ rejected: "Afgewezen",
1031
+ "not-executed": "Niet uitgevoerd"
1032
+ },
1033
+ "grant-source": {
1034
+ delegated: "Namens de starter",
1035
+ agent: "Agentrechten",
1036
+ "delegated-hint": "De agent gebruikte de rechten van de persoon die hem startte.",
1037
+ "agent-hint": "De agent gebruikte een recht dat hij zelf heeft gekregen, boven de rechten van de betrokkenen uit."
1038
+ },
1039
+ restricted: "Afgeschermd: de agent kreeg dit met eigen rechten, die jij niet hebt."
681
1040
  },
682
1041
  "tool-group": {
683
1042
  running: "Roept {{name}} aan…",
@@ -686,6 +1045,7 @@ var nl = {
686
1045
  "used-many": "{{count}} tools gebruikt",
687
1046
  progress: "{{finished}} van {{total}} klaar",
688
1047
  failed: "{{count}} mislukt",
1048
+ "not-executed": "{{count}} niet uitgevoerd",
689
1049
  "show-calls": "Tool-aanroepen tonen",
690
1050
  "hide-calls": "Tool-aanroepen verbergen"
691
1051
  },
@@ -707,10 +1067,65 @@ var nl = {
707
1067
  pending: "Open",
708
1068
  approved: "Goedgekeurd",
709
1069
  rejected: "Afgewezen",
710
- expired: "Verlopen"
1070
+ expired: "Verlopen",
1071
+ withdrawn: "Ingetrokken",
1072
+ stale: "Verouderd",
1073
+ executing: "Wordt uitgevoerd",
1074
+ executed: "Uitgevoerd",
1075
+ "execution-failed": "Mislukt",
1076
+ "execution-unknown": "Uitkomst onbekend",
1077
+ resolved: "Afgehandeld",
1078
+ shadow: "Schaduw"
711
1079
  },
712
1080
  "waiting-for": "Wacht op de beslissing van {{name}}. De actie gebruikt diens rechten.",
713
- "waiting-for-participant": "Wacht op de beslissing van de deelnemer die deze taak startte."
1081
+ "waiting-for-participant": "Wacht op de beslissing van de deelnemer die deze taak startte.",
1082
+ "waiting-for-approver": "Wacht tot een beslisser hierover beslist.",
1083
+ "waiting-for-other-approver": "Jij startte dit werk, dus een andere beslisser beslist hierover.",
1084
+ "give-feedback": "Wat klopte er niet?",
1085
+ "stale-kind": {
1086
+ "already-done": "Al gedaan",
1087
+ changed: "Gewijzigd",
1088
+ "not-applicable": "Niet meer nodig"
1089
+ },
1090
+ rationale: "Waarom: {{rationale}}",
1091
+ superseded: "Vervangen door een nieuwer voorstel"
1092
+ },
1093
+ "proposal-feedback": {
1094
+ legend: "Wat klopte er niet? (optioneel)",
1095
+ bulk: "Geldt voor de {{count}} voorstellen die je hebt afgewezen.",
1096
+ categories: {
1097
+ incorrect: "Onjuist",
1098
+ "wrong-value": "Verkeerde waarde",
1099
+ "not-needed": "Niet nodig",
1100
+ "not-now": "Niet nu",
1101
+ "already-done": "Al gedaan",
1102
+ "not-allowed": "Niet toegestaan",
1103
+ other: "Anders"
1104
+ },
1105
+ hints: {
1106
+ incorrect: "Het voorstel klopt inhoudelijk niet.",
1107
+ "wrong-value": "De juiste actie, met de verkeerde waarden.",
1108
+ "not-needed": "De wijziging is overbodig.",
1109
+ "not-now": "Juist, maar niet op dit moment.",
1110
+ "already-done": "Al gedaan of elders in behandeling.",
1111
+ "not-allowed": "In strijd met beleid, of meer dan de agent mag doen.",
1112
+ other: "Licht het toe in de tekst hieronder."
1113
+ },
1114
+ groups: {
1115
+ wrong: "De agent zat ernaast",
1116
+ "right-but-not-now": "Juist, maar niet nu",
1117
+ "not-allowed": "Niet toegestaan",
1118
+ other: "Anders"
1119
+ },
1120
+ "text-label": "Nog iets toe te voegen? (optioneel)",
1121
+ "text-hint": "De beheerders van de agent lezen dit om hem te verbeteren.",
1122
+ characters: "{{count}}/{{max}}",
1123
+ send: "Versturen",
1124
+ sending: "Versturen…",
1125
+ skip: "Overslaan",
1126
+ cancel: "Annuleren",
1127
+ thanks: "Bedankt voor je feedback",
1128
+ partial: "Opgeslagen voor {{stored}} van {{total}} voorstellen; de andere zijn intussen gewijzigd."
714
1129
  },
715
1130
  "result-codes": {
716
1131
  "api-bridge-validation": "Het verzoek was niet geldig.",
@@ -719,7 +1134,15 @@ var nl = {
719
1134
  "api-bridge-not-found": "Het item is niet gevonden.",
720
1135
  "api-bridge-conflict": "Het item is intussen gewijzigd.",
721
1136
  "api-bridge-upstream": "De dienst is niet beschikbaar.",
722
- "api-bridge-timeout": "Het verzoek duurde te lang."
1137
+ "api-bridge-timeout": "Het verzoek duurde te lang.",
1138
+ "assistant-proposal-recorded": "Voorgesteld; wacht op goedkeuring van een persoon",
1139
+ "assistant-proposal-rejected": "Niet uitgevoerd: het voorstel is afgewezen.",
1140
+ "assistant-proposal-expired": "Niet uitgevoerd: niemand besliste voordat het voorstel verliep.",
1141
+ "assistant-proposal-withdrawn": "Niet uitgevoerd: het voorstel is ingetrokken.",
1142
+ "assistant-proposal-stale": "Niet uitgevoerd: het voorstel was niet meer van toepassing.",
1143
+ "assistant-proposal-execution-failed": "Het goedgekeurde voorstel kon niet worden uitgevoerd.",
1144
+ "assistant-proposal-already-open": "Niet opnieuw voorgesteld: hetzelfde voorstel wacht al.",
1145
+ "assistant-proposal-recently-rejected": "Niet opnieuw voorgesteld: het is kort geleden afgewezen."
723
1146
  },
724
1147
  errors: {
725
1148
  generic: "Er ging iets mis. Probeer het opnieuw.",
@@ -778,7 +1201,43 @@ var nl = {
778
1201
  "assistant-directory-unavailable": "Collega’s direct uitnodigen is niet beschikbaar.",
779
1202
  "assistant-push-unavailable": "Bureaubladmeldingen zijn niet beschikbaar.",
780
1203
  "assistant-push-subscription-invalid": "Deze browser kon niet worden aangemeld voor meldingen.",
781
- "assistant-conversation-removed": "Dit gesprek is niet meer voor je beschikbaar."
1204
+ "assistant-conversation-removed": "Dit gesprek is niet meer voor je beschikbaar.",
1205
+ "assistant-subject-not-found": "Het onderwerp bestaat niet meer.",
1206
+ "assistant-subject-type-unknown": "Dit soort onderwerp kent de assistent niet.",
1207
+ "assistant-subject-access-denied": "Je rol op dit onderwerp staat dit niet toe.",
1208
+ "assistant-no-accountable-owner": "Niemand staat voor deze agent op dit onderwerp in.",
1209
+ "assistant-agent-disabled": "Deze agent is stopgezet.",
1210
+ "assistant-agent-not-configured": "Deze agent kan niet zelfstandig werken.",
1211
+ "assistant-run-not-found": "De run is niet gevonden.",
1212
+ "assistant-run-not-retryable": "Deze run kan niet opnieuw worden gestart.",
1213
+ "assistant-approval-policy-denied": "Je mag over dit voorstel niet beslissen.",
1214
+ "assistant-four-eyes-required": "Iemand anders dan de starter moet hierover beslissen.",
1215
+ "assistant-execution-not-uncertain": "Deze uitvoering wacht niet op afhandeling.",
1216
+ "assistant-proposal-stale": "Dit voorstel is niet meer van toepassing, bijvoorbeeld omdat het werk al gedaan is. Er is niets gewijzigd.",
1217
+ "assistant-proposal-check-failed": "Kon niet controleren of dit voorstel nog van toepassing is, dus er is niets gewijzigd. Probeer het later opnieuw.",
1218
+ "assistant-proposal-already-open": "Hetzelfde voorstel wacht al op een besluit.",
1219
+ "assistant-proposal-recently-rejected": "Hetzelfde voorstel is kort geleden afgewezen.",
1220
+ "assistant-proposal-superseded": "Een nieuwer voorstel heeft dit voorstel vervangen.",
1221
+ "assistant-proposal-refused": "Deze actie kan niet door een automatische regel worden voorgesteld.",
1222
+ "assistant-trigger-not-found": "De trigger is niet gevonden.",
1223
+ "assistant-webhook-signature-invalid": "De webhook-aanroep heeft geen geldige handtekening.",
1224
+ "assistant-webhook-payload-invalid": "De webhook-aanroep kon niet worden gelezen.",
1225
+ "assistant-memory-not-configured": "Deze agent houdt geen werkgeheugen bij.",
1226
+ "assistant-memory-invalid": "De wijziging van het werkgeheugen past niet bij het schema.",
1227
+ "assistant-memory-conflict": "Het werkgeheugen is tegelijk gewijzigd.",
1228
+ "assistant-artifact-invalid": "Het bestand is ongeldig of te groot.",
1229
+ "assistant-artifact-outside-turn": "Bestanden kunnen alleen worden opgeslagen terwijl de agent werkt.",
1230
+ "assistant-artifact-not-found": "Het bestand is niet gevonden.",
1231
+ "assistant-feedback-invalid": "Kies een van de genoemde redenen en houd de toelichting onder de 1.000 tekens.",
1232
+ "assistant-feedback-not-accepted": "Feedback kan pas nadat een voorstel is afgewezen of gesloten.",
1233
+ "assistant-consult-subject-unavailable": "Het onderwerp bestaat niet of je mag de agent er niet over raadplegen.",
1234
+ "assistant-consult-agent-unavailable": "De agent kan nu niet worden geraadpleegd.",
1235
+ "assistant-consult-depth-exceeded": "Deze raadpleging zou agents in een kring of te diep laten vragen.",
1236
+ "assistant-consult-budget-exhausted": "Het dagbudget van de agent is op.",
1237
+ "assistant-consult-limit-reached": "Je hebt het aantal raadplegingen voor vandaag bereikt.",
1238
+ "assistant-consult-timeout": "De agent antwoordde niet op tijd.",
1239
+ "assistant-consult-failed": "De agent kon niet antwoorden.",
1240
+ "assistant-consult-read-only": "Een raadpleging kan niets wijzigen."
782
1241
  },
783
1242
  notices: {
784
1243
  "no-answer": "De assistent is klaar zonder antwoord. Probeer het opnieuw of stel je vraag anders.",
@@ -892,7 +1351,8 @@ var nl = {
892
1351
  propose: "Voorstellen",
893
1352
  simulate: "Simuleren",
894
1353
  execute: "Uitvoeren"
895
- }
1354
+ },
1355
+ feedback: "Feedback"
896
1356
  },
897
1357
  "agent-editor": {
898
1358
  "new-title": "Nieuwe agent",
@@ -1006,6 +1466,59 @@ var nl = {
1006
1466
  "missing-warning": "De server biedt deze tool niet meer aan.",
1007
1467
  save: "Tool opslaan",
1008
1468
  back: "Terug naar de servers"
1469
+ },
1470
+ feedback: {
1471
+ title: "Feedback op voorstellen",
1472
+ description: "Wat mensen zeiden toen ze voorstellen van deze agent afwezen. Vergelijk agentversies om te zien of een wijziging van de prompt, tools of het beleid hielp.",
1473
+ agent: "{{name}} ({{id}})",
1474
+ back: "Terug naar agents",
1475
+ refresh: "Vernieuwen",
1476
+ "export": "Exporteren (JSON Lines)",
1477
+ exporting: "Exporteren…",
1478
+ "export-hint": "De export bevat gegevens van onderwerpen en de voorgestelde toolinvoer, en wordt vastgelegd in de auditlog.",
1479
+ period: "Periode",
1480
+ periods: {
1481
+ "last-7-days": "Afgelopen 7 dagen",
1482
+ "last-30-days": "Afgelopen 30 dagen",
1483
+ "last-90-days": "Afgelopen 90 dagen"
1484
+ },
1485
+ tool: "Tool",
1486
+ "all-tools": "Alle tools",
1487
+ category: "Categorie",
1488
+ "all-categories": "Alle categorieën",
1489
+ "category-hint": "De categorie beperkt de feedback en de export.",
1490
+ "summary-title": "Per tool en agentversie",
1491
+ "summary-empty": "De agent deed in deze periode geen voorstellen.",
1492
+ columns: {
1493
+ tool: "Tool",
1494
+ version: "Agentversie",
1495
+ approved: "Goedgekeurd",
1496
+ rejected: "Afgewezen",
1497
+ expired: "Verlopen",
1498
+ "rejection-rate": "Afwijzingspercentage",
1499
+ "with-feedback": "Afgewezen met feedback",
1500
+ invalidated: "Niet meer nodig",
1501
+ people: "Personen",
1502
+ categories: "Meest gekozen"
1503
+ },
1504
+ version: "v{{version}}",
1505
+ consultations: "Raadplegingen",
1506
+ "no-decisions": "Nog niet beslist",
1507
+ "no-categories": "Geen",
1508
+ "invalidated-hint": "Open voorstellen die sloten voordat iemand besliste, omdat het werk al gedaan was of niet meer van toepassing was. Ze tellen niet als afgewezen.",
1509
+ "list-title": "Feedback",
1510
+ "list-count": "{{count}} in totaal",
1511
+ "list-empty": "Niemand gaf in deze periode feedback.",
1512
+ "list-empty-filtered": "Geen feedback past bij deze filters.",
1513
+ "no-text": "Geen toelichting gegeven.",
1514
+ proposal: "Voorstel:",
1515
+ author: "Door {{actor}}",
1516
+ bulk: "In bulk",
1517
+ "bulk-hint": "Eén keer gegeven voor meerdere voorstellen die samen zijn afgewezen; weeg dit lager.",
1518
+ previous: "Vorige",
1519
+ next: "Volgende",
1520
+ page: "Pagina {{page}} van {{pages}}",
1521
+ pages: "Pagina's"
1009
1522
  }
1010
1523
  },
1011
1524
  "page-context": {
@@ -1052,6 +1565,159 @@ var nl = {
1052
1565
  remove: "{{name}} verwijderen",
1053
1566
  leave: "Gesprek verlaten",
1054
1567
  "no-conversation": "Start of open een gesprek om het te delen."
1568
+ },
1569
+ "agent-runs": {
1570
+ loading: "Laden…",
1571
+ "load-failed": "Dit kon niet worden geladen.",
1572
+ retry: "Opnieuw proberen",
1573
+ refresh: "Vernieuwen",
1574
+ role: {
1575
+ none: "Geen rol op dit onderwerp",
1576
+ reader: "Lezer",
1577
+ participant: "Deelnemer",
1578
+ approver: "Beslisser"
1579
+ },
1580
+ thread: {
1581
+ title: "Agent",
1582
+ "waiting-inputs": "{{count}} berichten wachten op de volgende run",
1583
+ "open-conversation": "Gesprek openen",
1584
+ "start-message": "Instructie voor de agent (optioneel)",
1585
+ start: "Agent starten",
1586
+ starting: "Starten…",
1587
+ "no-proposals": "De agent heeft hier nog niets voorgesteld.",
1588
+ "no-thread": "De agent heeft hier nog niet aan gewerkt.",
1589
+ tabs: {
1590
+ proposals: "Voorstellen",
1591
+ runs: "Runs",
1592
+ consultations: "Raadplegingen",
1593
+ memory: "Werkgeheugen",
1594
+ artifacts: "Bestanden"
1595
+ },
1596
+ outcome: {
1597
+ started: "De agent start zo.",
1598
+ queued: "De agent start na de huidige run.",
1599
+ coalesced: "Je verzoek sluit aan bij de run die zo start.",
1600
+ superseded: "De huidige run stopt en de agent start opnieuw.",
1601
+ ignored: "De agent is bezig; er is niets gestart.",
1602
+ deduplicated: "Dit was al gestart.",
1603
+ rejected: "De agent kon niet starten."
1604
+ }
1605
+ },
1606
+ subject: {
1607
+ title: "Voorstellen van agents",
1608
+ description: "Voorstellen die agents hier deden en die op een besluit wachten, de eerste die verloopt bovenaan.",
1609
+ empty: "Geen openstaande voorstellen van agents.",
1610
+ unavailable: "Je kunt het werk van agents hieraan niet zien.",
1611
+ agents: "Agents",
1612
+ idle: "Werkt nu niet",
1613
+ opening: "Openen…",
1614
+ "open-conversation-label": "Gesprek openen met {{agent}}",
1615
+ "start-label": "Agent starten: {{agent}}",
1616
+ "assistant-unavailable": "De assistent is niet beschikbaar."
1617
+ },
1618
+ inbox: {
1619
+ title: "Voorstellen om te beslissen",
1620
+ description: "Voorstellen van agents die op jouw besluit wachten, de eerst verlopende bovenaan.",
1621
+ empty: "Er wachten geen voorstellen op jou.",
1622
+ select: "Dit voorstel selecteren",
1623
+ "select-all": "Alles selecteren",
1624
+ "approve-selected": "Selectie goedkeuren",
1625
+ "reject-selected": "Selectie afwijzen",
1626
+ previous: "Vorige",
1627
+ next: "Volgende",
1628
+ page: "Pagina {{page}} van {{pages}}",
1629
+ pages: "Pagina's"
1630
+ },
1631
+ proposal: {
1632
+ approve: "Goedkeuren",
1633
+ reject: "Afwijzen",
1634
+ expires: "Verloopt {{date}}",
1635
+ "decided-by": "Besloten door {{actor}}",
1636
+ "arguments": "Invoer",
1637
+ "re-execute": "Opnieuw uitvoeren",
1638
+ "mark-resolved": "Als afgehandeld markeren",
1639
+ "unknown-hint": "De uitvoering stopte zonder bekende uitkomst. Controleer of het gebeurd is voordat je het opnieuw uitvoert.",
1640
+ mode: {
1641
+ inline: "De run wacht",
1642
+ deferred: "Later beslissen"
1643
+ },
1644
+ status: {
1645
+ pending: "Wacht",
1646
+ approved: "Goedgekeurd",
1647
+ rejected: "Afgewezen",
1648
+ expired: "Verlopen",
1649
+ withdrawn: "Ingetrokken",
1650
+ stale: "Verouderd",
1651
+ executing: "Wordt uitgevoerd",
1652
+ executed: "Uitgevoerd",
1653
+ "execution-failed": "Mislukt",
1654
+ "execution-unknown": "Uitkomst onbekend",
1655
+ resolved: "Afgehandeld",
1656
+ shadow: "Schaduw"
1657
+ },
1658
+ "from-consultation": "Uit een raadpleging",
1659
+ "from-consultation-hint": "Een andere assistent raadpleegde de agent voor deze persoon; het voorstel draait alleen met de rechten van die persoon.",
1660
+ "give-feedback": "Feedback geven",
1661
+ "change-feedback": "Feedback wijzigen",
1662
+ "your-feedback": "Jouw feedback: {{category}}",
1663
+ "rejected-one": "Je hebt afgewezen: {{summary}}",
1664
+ closed: "Intussen gesloten",
1665
+ dismiss: "Wegklikken",
1666
+ "dismiss-label": "Gesloten voorstel wegklikken: {{summary}}",
1667
+ "show-successor": "Toon het nieuwere voorstel",
1668
+ "stale-by": "Ingehaald door {{actor}}"
1669
+ },
1670
+ run: {
1671
+ empty: "Nog geen runs.",
1672
+ status: {
1673
+ queued: "In de wachtrij",
1674
+ running: "Bezig",
1675
+ "waiting-for-approval": "Wacht op goedkeuring",
1676
+ completed: "Klaar",
1677
+ failed: "Mislukt",
1678
+ cancelled: "Geannuleerd"
1679
+ },
1680
+ shadow: "Schaduw",
1681
+ kind: {
1682
+ rule: "Automatische regel",
1683
+ "rule-then-model": "Regel, daarna het model"
1684
+ },
1685
+ trigger: "Aanleiding: {{trigger}}",
1686
+ "tool-calls": "{{count}} tool-aanroepen",
1687
+ tokens: "{{count}} tokens",
1688
+ proposals: "{{count}} voorstellen",
1689
+ attempt: "Poging {{attempt}}",
1690
+ cancel: "Annuleren",
1691
+ resume: "Hervatten",
1692
+ restart: "Opnieuw starten"
1693
+ },
1694
+ memory: {
1695
+ "not-configured": "Deze agent houdt geen werkgeheugen bij.",
1696
+ version: "Schemaversie {{version}}",
1697
+ updated: "Bijgewerkt {{date}}",
1698
+ empty: "De agent heeft nog niets genoteerd."
1699
+ },
1700
+ artifacts: {
1701
+ "download-failed": "Het bestand kon niet worden gedownload.",
1702
+ empty: "Nog geen bestanden.",
1703
+ version: "v{{version}}",
1704
+ restricted: "Afgeschermd (agentrechten)",
1705
+ size: "{{size}} bytes",
1706
+ download: "Downloaden"
1707
+ },
1708
+ consultation: {
1709
+ empty: "Nog geen assistent heeft deze agent over dit onderwerp geraadpleegd.",
1710
+ "private": "Hier staat alleen wie vroeg en hoe het afliep; de vraag en het antwoord blijven in het vragende gesprek.",
1711
+ "asked-by": "Gevraagd door {{name}}",
1712
+ through: "via {{agent}}",
1713
+ depth: "Genest, niveau {{depth}}",
1714
+ more: "{{count}} oudere raadplegingen worden niet getoond.",
1715
+ status: {
1716
+ running: "Bezig",
1717
+ completed: "Beantwoord",
1718
+ failed: "Niet beantwoord"
1719
+ }
1720
+ }
1055
1721
  }
1056
1722
  }
1057
1723
  };
@@ -1231,7 +1897,8 @@ class NhAssistantTransport {
1231
1897
  }
1232
1898
  }
1233
1899
  async createInit(method, accept, body, signal) {
1234
- const headers = { Accept: accept };
1900
+ const headers = await nhAssistantRequestHeaders(this.config, this.injector);
1901
+ headers['Accept'] = accept;
1235
1902
  const token = await runInInjectionContext(this.injector, () => this.config.getAccessToken());
1236
1903
  if (token) {
1237
1904
  headers['Authorization'] = `Bearer ${token}`;
@@ -1264,6 +1931,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
1264
1931
  function createApiError(status, code) {
1265
1932
  return new NhAssistantApiError(status, code, nhAssistantErrorMessageKey(code));
1266
1933
  }
1934
+ const reservedRequestHeaders = new Set(['authorization', 'accept', 'accept-language', 'content-type']);
1935
+ /**
1936
+ * The host's extra request headers from `NhAssistantConfig.getRequestHeaders`, without the
1937
+ * headers the library sets itself and without empty values.
1938
+ */
1939
+ async function nhAssistantRequestHeaders(config, injector) {
1940
+ const headers = {};
1941
+ if (!config.getRequestHeaders) {
1942
+ return headers;
1943
+ }
1944
+ const extra = await runInInjectionContext(injector, () => config.getRequestHeaders());
1945
+ for (const [name, value] of Object.entries(extra ?? {})) {
1946
+ if (typeof value === 'string' && value.length > 0 && !reservedRequestHeaders.has(name.toLowerCase())) {
1947
+ headers[name] = value;
1948
+ }
1949
+ }
1950
+ return headers;
1951
+ }
1267
1952
 
1268
1953
  /** In the admin endpoints a 409 means a stale `expectedVersion` or a forbidden state change. */
1269
1954
  const adminStatusCodes = {
@@ -1337,6 +2022,37 @@ class NhAssistantAdminApiService {
1337
2022
  const path = `admin/mcp-servers/${encodeURIComponent(serverId)}/tools/${encodeURIComponent(remoteName)}`;
1338
2023
  return this.request('PUT', path, request);
1339
2024
  }
2025
+ /**
2026
+ * The agent's proposals per tool and agent version in the period: decisions, the rejection rate,
2027
+ * feedback per category and the number of different people who gave it.
2028
+ */
2029
+ getAgentFeedbackSummary(agentId, filter = {}) {
2030
+ return this.request('GET', withQuery(`${feedbackPath(agentId)}/summary`, filter));
2031
+ }
2032
+ /** The feedback on the agent's proposals with the proposal as approvers read it, newest change first. */
2033
+ getAgentFeedback(agentId, filter = {}) {
2034
+ return this.request('GET', withQuery(feedbackPath(agentId), filter));
2035
+ }
2036
+ /**
2037
+ * Downloads the feedback as JSON Lines with the tool input of each proposal. The file holds
2038
+ * subject data and the server audits the export. Rejects with `NhAssistantApiError`.
2039
+ */
2040
+ async exportAgentFeedback(agentId, filter = {}) {
2041
+ const path = withQuery(`${feedbackPath(agentId)}/export`, filter);
2042
+ let response;
2043
+ try {
2044
+ response = await this.transport.fetch('GET', path, 'application/x-ndjson', undefined, new AbortController().signal);
2045
+ }
2046
+ catch {
2047
+ const code = nhAssistantCodeForStatus(0);
2048
+ throw new NhAssistantApiError(0, code, nhAssistantErrorMessageKey(code));
2049
+ }
2050
+ if (!response.ok) {
2051
+ const code = await this.transport.failureCode(response, adminStatusCodes);
2052
+ throw new NhAssistantApiError(response.status, code, nhAssistantErrorMessageKey(code));
2053
+ }
2054
+ return response.blob();
2055
+ }
1340
2056
  request(method, path, body) {
1341
2057
  return this.transport.json(method, path, body, adminStatusCodes);
1342
2058
  }
@@ -1346,6 +2062,183 @@ class NhAssistantAdminApiService {
1346
2062
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminApiService, decorators: [{
1347
2063
  type: Injectable
1348
2064
  }] });
2065
+ function feedbackPath(agentId) {
2066
+ return `admin/agents/${encodeURIComponent(agentId)}/feedback`;
2067
+ }
2068
+ /** Appends the filter values that are set as query parameters. */
2069
+ function withQuery(path, filter) {
2070
+ const query = new URLSearchParams();
2071
+ for (const [key, value] of Object.entries(filter)) {
2072
+ if (value !== undefined && value !== null && value !== '') {
2073
+ query.set(key, String(value));
2074
+ }
2075
+ }
2076
+ const suffix = query.toString();
2077
+ return suffix ? `${path}?${suffix}` : path;
2078
+ }
2079
+
2080
+ /**
2081
+ * Client for the agent-run endpoints: the agent thread about a subject, its runs, proposals,
2082
+ * working memory and artifacts, and the approval inbox. Access follows the caller's role on the
2083
+ * subject, which the server checks on every request; failures error with `NhAssistantApiError`.
2084
+ */
2085
+ class NhAssistantAgentRunApiService {
2086
+ constructor() {
2087
+ this.transport = inject(NhAssistantTransport);
2088
+ }
2089
+ /** The agent's thread about the subject, the caller's role and whether the caller may start the agent. */
2090
+ getThread(agentId, subjectType, subjectId) {
2091
+ const query = new URLSearchParams({ agentId, subjectType, subjectId });
2092
+ return this.transport.json('GET', `threads?${query.toString()}`);
2093
+ }
2094
+ /**
2095
+ * The agent's thread about the subject, created without starting a run when it is missing, so
2096
+ * the caller can open the conversation and type there. Creating a thread needs the participant
2097
+ * role; readers may open an existing one. Errors with `assistant-subject-access-denied` otherwise.
2098
+ */
2099
+ openThread(agentId, subjectType, subjectId) {
2100
+ return this.transport.json('POST', 'threads/open', { agentId, subjectType, subjectId });
2101
+ }
2102
+ /** Starts the agent on the subject with the caller as initiator; needs the participant role. */
2103
+ startAgent(request) {
2104
+ return this.transport.json('POST', 'threads/start', request);
2105
+ }
2106
+ /**
2107
+ * All agent work on one subject: the caller's role, every agent with a thread on it plus
2108
+ * `agentId`, and the open proposals of all of them with the caller's live decision right. Errors
2109
+ * with `assistant-subject-not-found` when the caller has no role on the subject.
2110
+ */
2111
+ getSubjectWork(subjectType, subjectId, agentId) {
2112
+ const path = `subjects/${encodeURIComponent(subjectType)}/${encodeURIComponent(subjectId)}/work`;
2113
+ const query = agentId ? `?${new URLSearchParams({ agentId }).toString()}` : '';
2114
+ return this.transport.json('GET', path + query);
2115
+ }
2116
+ /**
2117
+ * Counts the agent work on at most `NH_ASSISTANT_MAX_SUBJECT_SUMMARY` subjects of one type in one
2118
+ * request, for badges in a list; one item per id in request order. `NhAssistantSubjectSummaryService`
2119
+ * batches and caches these requests for many badges.
2120
+ */
2121
+ summarizeSubjects(subjectType, subjectIds) {
2122
+ return this.transport.json('POST', `subjects/${encodeURIComponent(subjectType)}/summary`, {
2123
+ subjectIds
2124
+ });
2125
+ }
2126
+ getRuns(conversationId) {
2127
+ return this.transport.json('GET', `threads/${encodeURIComponent(conversationId)}/runs`);
2128
+ }
2129
+ /** Who consulted the thread's agent through another assistant, newest first. */
2130
+ getConsultations(conversationId, page = 1, itemsPerPage = 20) {
2131
+ const query = new URLSearchParams({ page: String(page), itemsPerPage: String(itemsPerPage) });
2132
+ return this.transport.json('GET', `threads/${encodeURIComponent(conversationId)}/consultations?${query.toString()}`);
2133
+ }
2134
+ getProposals(conversationId) {
2135
+ return this.transport.json('GET', `threads/${encodeURIComponent(conversationId)}/proposals`);
2136
+ }
2137
+ /**
2138
+ * One proposal of an agent thread in any status, with the caller's live right to decide it, for
2139
+ * example the one an `AgentWorkChanged` update names. Fails with `assistant-approval-not-found`
2140
+ * for an unknown proposal and for one the caller may not see.
2141
+ */
2142
+ getProposal(approvalId) {
2143
+ return this.transport.json('GET', `approvals/${encodeURIComponent(approvalId)}`);
2144
+ }
2145
+ /**
2146
+ * The proposals among at most `NH_ASSISTANT_MAX_PROPOSAL_LOOKUP` ids that the caller may see, in
2147
+ * the order of the ids, each with the caller's live right to decide it. The others are left out.
2148
+ */
2149
+ lookupProposals(approvalIds) {
2150
+ return this.transport.json('POST', 'approvals/lookup', { approvalIds });
2151
+ }
2152
+ getMemory(conversationId) {
2153
+ return this.transport.json('GET', `threads/${encodeURIComponent(conversationId)}/memory`);
2154
+ }
2155
+ getArtifacts(conversationId) {
2156
+ return this.transport.json('GET', `threads/${encodeURIComponent(conversationId)}/artifacts`);
2157
+ }
2158
+ /** Downloads one artifact version with the host's credentials. */
2159
+ downloadArtifact(conversationId, artifactId) {
2160
+ const path = `threads/${encodeURIComponent(conversationId)}/artifacts/${encodeURIComponent(artifactId)}/content`;
2161
+ return this.transport.fetch('GET', path, '*/*', undefined, new AbortController().signal).then(response => {
2162
+ if (!response.ok) {
2163
+ throw new Error(`Artifact download failed with status ${response.status}.`);
2164
+ }
2165
+ return response.blob();
2166
+ });
2167
+ }
2168
+ /** Cancels a run at its next safe point and withdraws its open proposals; needs the participant role. */
2169
+ cancelRun(runId) {
2170
+ return this.transport.json('POST', `runs/${encodeURIComponent(runId)}/cancel`);
2171
+ }
2172
+ /** Resumes or restarts a failed or cancelled run; needs the approver role. */
2173
+ retryRun(runId, mode) {
2174
+ return this.transport.json('POST', `runs/${encodeURIComponent(runId)}/retry`, { mode });
2175
+ }
2176
+ /** Pending proposals the caller may decide, sorted by expiry. */
2177
+ getInbox(filter = {}) {
2178
+ const query = new URLSearchParams();
2179
+ for (const [key, value] of Object.entries(filter)) {
2180
+ if (value !== undefined && value !== null && value !== '') {
2181
+ query.set(key, String(value));
2182
+ }
2183
+ }
2184
+ const suffix = query.toString();
2185
+ return this.transport.json('GET', suffix ? `approvals/inbox?${suffix}` : 'approvals/inbox');
2186
+ }
2187
+ /**
2188
+ * Decides one proposal, bound to the proposal hash the person saw; the server checks the approval
2189
+ * policy and the tool's approve policy again. Accepts an inbox proposal or the approval part of an
2190
+ * agent thread.
2191
+ */
2192
+ decide(proposal, decision, reason) {
2193
+ return this.transport.json('POST', `approvals/${encodeURIComponent(proposal.approvalId)}/decisions`, {
2194
+ decision,
2195
+ expectedProposalHash: proposal.proposalHash,
2196
+ reason: reason?.trim() || null
2197
+ });
2198
+ }
2199
+ /** Decides several proposals; each one is checked and decided on its own. */
2200
+ decideMany(approvalIds, decision, reason) {
2201
+ return this.transport.json('POST', 'approvals/decisions', {
2202
+ approvalIds,
2203
+ decision,
2204
+ reason: reason?.trim() || null
2205
+ });
2206
+ }
2207
+ /** Resolves an execution whose outcome is unknown. */
2208
+ resolve(approvalId, action, reason) {
2209
+ return this.transport.json('POST', `approvals/${encodeURIComponent(approvalId)}/resolve`, {
2210
+ action,
2211
+ reason: reason?.trim() || null
2212
+ });
2213
+ }
2214
+ /**
2215
+ * Gives or changes the caller's feedback on a closed proposal (`canGiveFeedback`). Feedback is
2216
+ * optional and separate from the decision; an empty text is sent as `null`. Fails with
2217
+ * `assistant-feedback-not-accepted` while the proposal is pending or after it was approved.
2218
+ */
2219
+ giveFeedback(approvalId, category, text) {
2220
+ return this.transport.json('PUT', `approvals/${encodeURIComponent(approvalId)}/feedback`, {
2221
+ category,
2222
+ text: text?.trim() || null
2223
+ });
2224
+ }
2225
+ /**
2226
+ * Gives the same feedback on at most `NH_ASSISTANT_MAX_BULK_FEEDBACK` proposals that were
2227
+ * rejected together. Each proposal is checked on its own and stored as bulk feedback.
2228
+ */
2229
+ giveFeedbackMany(approvalIds, category, text) {
2230
+ return this.transport.json('POST', 'approvals/feedback', {
2231
+ approvalIds,
2232
+ category,
2233
+ text: text?.trim() || null
2234
+ });
2235
+ }
2236
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentRunApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2237
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentRunApiService }); }
2238
+ }
2239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentRunApiService, decorators: [{
2240
+ type: Injectable
2241
+ }] });
1349
2242
 
1350
2243
  /**
1351
2244
  * Incremental parser for the `text/event-stream` format.
@@ -1614,10 +2507,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
1614
2507
 
1615
2508
  const reconnectDelaysMs = [0, 2_000, 5_000, 10_000, 30_000];
1616
2509
  const restartDelayMs = 30_000;
2510
+ /** Query parameters the library and SignalR set themselves. */
2511
+ const reservedHubQueryParameters = new Set(['access_token', 'id', 'negotiateversion']);
1617
2512
  /**
1618
- * Live updates of the assistant hub: conversation changes, read positions, removals and the
1619
- * turn events of other sessions. Connects only while the store asks for it, reconnects on its
1620
- * own and emits `resync` after every (re)connect, because updates sent while disconnected are lost.
2513
+ * Live updates of the assistant hub: conversation changes, read positions, removals, the turn
2514
+ * events of other sessions and changes to the work of agents that run on their own. Connects only
2515
+ * while the store asks for it, reconnects on its own and emits `resync` after every (re)connect,
2516
+ * because updates sent while disconnected are lost.
1621
2517
  */
1622
2518
  class NhAssistantLiveService {
1623
2519
  constructor() {
@@ -1628,6 +2524,7 @@ class NhAssistantLiveService {
1628
2524
  this.readSubject = new Subject();
1629
2525
  this.removedSubject = new Subject();
1630
2526
  this.eventSubject = new Subject();
2527
+ this.agentWorkSubject = new Subject();
1631
2528
  this.resyncSubject = new Subject();
1632
2529
  this.stateSignal = signal('off', ...(ngDevMode ? [{ debugName: "stateSignal" }] : []));
1633
2530
  this.hubPath = null;
@@ -1637,6 +2534,11 @@ class NhAssistantLiveService {
1637
2534
  this.read$ = this.readSubject.asObservable();
1638
2535
  this.removed$ = this.removedSubject.asObservable();
1639
2536
  this.events$ = this.eventSubject.asObservable();
2537
+ /**
2538
+ * A run, proposal or feedback of an agent thread changed. It carries no content; components
2539
+ * that show the thread reload it.
2540
+ */
2541
+ this.agentWork$ = this.agentWorkSubject.asObservable();
1640
2542
  /** Reload snapshots: emitted after every (re)connect. */
1641
2543
  this.resync$ = this.resyncSubject.asObservable();
1642
2544
  this.state = this.stateSignal.asReadonly();
@@ -1646,6 +2548,7 @@ class NhAssistantLiveService {
1646
2548
  this.readSubject.complete();
1647
2549
  this.removedSubject.complete();
1648
2550
  this.eventSubject.complete();
2551
+ this.agentWorkSubject.complete();
1649
2552
  this.resyncSubject.complete();
1650
2553
  });
1651
2554
  }
@@ -1701,9 +2604,12 @@ class NhAssistantLiveService {
1701
2604
  }
1702
2605
  async build(url) {
1703
2606
  const signalR = await import('@microsoft/signalr');
2607
+ const headers = await nhAssistantRequestHeaders(this.config, this.injector);
2608
+ const hubUrl = await this.withQueryParameters(url);
1704
2609
  const connection = new signalR.HubConnectionBuilder()
1705
- .withUrl(url, {
1706
- accessTokenFactory: async () => (await this.token()) ?? ''
2610
+ .withUrl(hubUrl, {
2611
+ accessTokenFactory: async () => (await this.token()) ?? '',
2612
+ headers
1707
2613
  })
1708
2614
  .withAutomaticReconnect(reconnectDelaysMs)
1709
2615
  .configureLogging(signalR.LogLevel.None)
@@ -1712,6 +2618,7 @@ class NhAssistantLiveService {
1712
2618
  connection.on('ConversationRead', (message) => this.readSubject.next(message));
1713
2619
  connection.on('ConversationRemoved', (message) => this.removedSubject.next(message));
1714
2620
  connection.on('ConversationEvent', (message) => this.eventSubject.next(message));
2621
+ connection.on('AgentWorkChanged', (message) => this.agentWorkSubject.next(message));
1715
2622
  connection.onreconnecting(() => this.stateSignal.set('reconnecting'));
1716
2623
  connection.onreconnected(() => {
1717
2624
  this.stateSignal.set('connected');
@@ -1743,6 +2650,30 @@ class NhAssistantLiveService {
1743
2650
  return null;
1744
2651
  }
1745
2652
  }
2653
+ /**
2654
+ * The hub URL with the host's `getHubQueryParameters`, which reach the hub on every transport,
2655
+ * unlike headers on WebSockets and server-sent events.
2656
+ */
2657
+ async withQueryParameters(url) {
2658
+ const getter = this.config.getHubQueryParameters;
2659
+ if (!getter) {
2660
+ return url;
2661
+ }
2662
+ let parameters;
2663
+ try {
2664
+ parameters = await runInInjectionContext(this.injector, () => getter());
2665
+ }
2666
+ catch {
2667
+ return url;
2668
+ }
2669
+ const target = new URL(url);
2670
+ for (const [name, value] of Object.entries(parameters ?? {})) {
2671
+ if (typeof value === 'string' && value.length > 0 && !reservedHubQueryParameters.has(name.toLowerCase())) {
2672
+ target.searchParams.set(name, value);
2673
+ }
2674
+ }
2675
+ return target.toString();
2676
+ }
1746
2677
  /** `hubBaseUrl` + `hubPath`, or the hub path on the origin of `apiBaseUrl`. */
1747
2678
  resolveUrl(hubPath) {
1748
2679
  const page = globalThis.location?.href ?? 'http://localhost';
@@ -2399,6 +3330,7 @@ function validId(value) {
2399
3330
 
2400
3331
  /** Notice code for a completed turn that produced no answer text and no server code. */
2401
3332
  const NH_ASSISTANT_NO_ANSWER_NOTICE_CODE = 'assistant-no-answer';
3333
+ const fourEyesRequiredCode = 'assistant-four-eyes-required';
2402
3334
  /** True when a conversation has messages the caller has not read. A running turn is not unread yet. */
2403
3335
  function nhAssistantIsUnread(summary) {
2404
3336
  if (summary.status === 'running' || summary.lastReadSequence === undefined) {
@@ -2420,6 +3352,7 @@ const newConversationKey = '';
2420
3352
  class NhAssistantStore {
2421
3353
  constructor() {
2422
3354
  this.api = inject(NhAssistantApiService);
3355
+ this.agentRuns = inject(NhAssistantAgentRunApiService);
2423
3356
  this.config = inject(NH_ASSISTANT_CONFIG);
2424
3357
  this.injector = inject(EnvironmentInjector);
2425
3358
  this.accessPolicy = inject(NH_ASSISTANT_ACCESS_POLICY);
@@ -2452,9 +3385,13 @@ class NhAssistantStore {
2452
3385
  this.queueDispatchState = signal(null, ...(ngDevMode ? [{ debugName: "queueDispatchState" }] : []));
2453
3386
  this.steeringState = signal(null, ...(ngDevMode ? [{ debugName: "steeringState" }] : []));
2454
3387
  this.submittingState = signal(false, ...(ngDevMode ? [{ debugName: "submittingState" }] : []));
3388
+ /** Agent proposals the current user rejected in this session and may give feedback on. */
3389
+ this.feedbackApprovalsState = signal(new Set(), ...(ngDevMode ? [{ debugName: "feedbackApprovalsState" }] : []));
2455
3390
  this.destroyed = false;
2456
3391
  this.streams = new Map();
2457
3392
  this.cancelTimers = new Map();
3393
+ /** Pending reloads of agent threads after live agent-work updates, per conversation. */
3394
+ this.agentWorkTimers = new Map();
2458
3395
  this.pendingSendResolves = new Map();
2459
3396
  this.markingRead = new Set();
2460
3397
  this.liveSubscription = new Subscription();
@@ -2530,13 +3467,61 @@ class NhAssistantStore {
2530
3467
  this.members = computed(() => this.activeConversation()?.members ?? [], ...(ngDevMode ? [{ debugName: "members" }] : []));
2531
3468
  /** True when the current user owns the open conversation (or it is new). */
2532
3469
  this.isOwner = computed(() => (this.activeConversation()?.role ?? 'owner') === 'owner', ...(ngDevMode ? [{ debugName: "isOwner" }] : []));
2533
- /** True when the current user may decide the pending approval: it belongs to their own turn. */
2534
- this.canDecide = computed(() => {
3470
+ /** True when the open conversation is the durable thread of an agent about a subject. */
3471
+ this.isAgentThread = computed(() => this.activeConversation()?.kind === 'thread', ...(ngDevMode ? [{ debugName: "isAgentThread" }] : []));
3472
+ /**
3473
+ * The current user's right to decide each pending approval of the open conversation, by approval
3474
+ * id. In a chat only the author of the waiting turn decides. In an agent thread the subject's
3475
+ * approvers decide, as the server reports per proposal: the approval policy, four-eyes and the
3476
+ * tool's approve policy, checked live. Proposals the user rejected in this session carry
3477
+ * `canGiveFeedback`.
3478
+ */
3479
+ this.approvalRights = computed(() => {
2535
3480
  const conversation = this.activeConversation();
2536
- if (!conversation?.pendingApproval) {
2537
- return false;
3481
+ const rights = new Map();
3482
+ if (!conversation) {
3483
+ return rights;
2538
3484
  }
2539
- return !conversation.activeActorId || !conversation.currentActorId || conversation.activeActorId === conversation.currentActorId;
3485
+ if (conversation.kind === 'thread') {
3486
+ const decisions = new Map((conversation.thread?.decisions ?? []).map(decision => [decision.approvalId, decision]));
3487
+ for (const approval of pendingApprovalParts(conversation)) {
3488
+ const decision = decisions.get(approval.approvalId);
3489
+ rights.set(approval.approvalId, {
3490
+ canDecide: decision?.canDecide === true,
3491
+ waitingFor: decision?.reasonCode === fourEyesRequiredCode ? 'other-approver' : 'approver',
3492
+ decisionOwner: null
3493
+ });
3494
+ }
3495
+ const feedbackApprovals = this.feedbackApprovalsState();
3496
+ for (const approval of approvalParts(conversation)) {
3497
+ if (approval.status === 'rejected' && feedbackApprovals.has(approval.approvalId)) {
3498
+ rights.set(approval.approvalId, {
3499
+ canDecide: false,
3500
+ waitingFor: 'approver',
3501
+ decisionOwner: null,
3502
+ canGiveFeedback: true
3503
+ });
3504
+ }
3505
+ }
3506
+ return rights;
3507
+ }
3508
+ if (conversation.pendingApproval) {
3509
+ rights.set(conversation.pendingApproval.approvalId, {
3510
+ canDecide: !conversation.activeActorId || !conversation.currentActorId
3511
+ || conversation.activeActorId === conversation.currentActorId,
3512
+ waitingFor: 'turn',
3513
+ decisionOwner: this.activeMember()?.displayName ?? null
3514
+ });
3515
+ }
3516
+ return rights;
3517
+ }, ...(ngDevMode ? [{ debugName: "approvalRights" }] : []));
3518
+ /**
3519
+ * True when the current user may decide the pending approval: in a chat it belongs to their own
3520
+ * turn, in an agent thread the server allows them as an approver.
3521
+ */
3522
+ this.canDecide = computed(() => {
3523
+ const approval = this.activeConversation()?.pendingApproval;
3524
+ return approval ? this.approvalRights().get(approval.approvalId)?.canDecide === true : false;
2540
3525
  }, ...(ngDevMode ? [{ debugName: "canDecide" }] : []));
2541
3526
  /** The member whose turn runs or waits in the open conversation, when it is someone else. */
2542
3527
  this.activeMember = computed(() => {
@@ -2577,6 +3562,7 @@ class NhAssistantStore {
2577
3562
  this.accessSubscription?.unsubscribe();
2578
3563
  this.liveSubscription.unsubscribe();
2579
3564
  this.stopAllTurns();
3565
+ this.clearAgentWorkTimers();
2580
3566
  clearTimeout(this.refreshTimer);
2581
3567
  });
2582
3568
  effect(() => {
@@ -2607,6 +3593,7 @@ class NhAssistantStore {
2607
3593
  this.liveSubscription.add(this.live.read$.subscribe(read => this.onLiveRead(read)));
2608
3594
  this.liveSubscription.add(this.live.removed$.subscribe(removed => this.onLiveRemoved(removed)));
2609
3595
  this.liveSubscription.add(this.live.events$.subscribe(event => this.onLiveEvent(event)));
3596
+ this.liveSubscription.add(this.live.agentWork$.subscribe(change => this.onLiveAgentWork(change)));
2610
3597
  this.liveSubscription.add(this.live.resync$.subscribe(() => this.resync()));
2611
3598
  if (this.browser) {
2612
3599
  const update = () => this.pageVisibleState.set(this.document.visibilityState === 'visible' && this.document.hasFocus());
@@ -2964,12 +3951,31 @@ class NhAssistantStore {
2964
3951
  this.sendingState.set(null);
2965
3952
  });
2966
3953
  }
2967
- /** Approves or rejects the pending approval. Ignores repeated calls while a decision is in flight. */
2968
- decide(decision, reason) {
3954
+ /**
3955
+ * Approves or rejects an approval of the open conversation: `approvalId`, or the pending approval
3956
+ * when it is left out. A proposal of an agent's run is decided with its proposal hash through the
3957
+ * agent-run endpoint; other approvals resume their turn as a stream. Ignores calls the current
3958
+ * user may not make and repeated calls while a decision is in flight.
3959
+ */
3960
+ decide(decision, reason, approvalId) {
2969
3961
  const session = this.activeSession();
2970
3962
  const conversation = session?.conversation;
2971
- const approval = conversation?.pendingApproval ?? null;
2972
- if (!session || !conversation || !approval || session.deciding || session.streaming || !this.canDecide()) {
3963
+ if (!session || !conversation || session.deciding || session.streaming) {
3964
+ return;
3965
+ }
3966
+ const approval = approvalId
3967
+ ? pendingApprovalParts(conversation).find(part => part.approvalId === approvalId) ?? null
3968
+ : conversation.pendingApproval;
3969
+ if (!approval || this.approvalRights().get(approval.approvalId)?.canDecide !== true) {
3970
+ return;
3971
+ }
3972
+ const proposal = conversation.thread?.decisions.find(item => item.approvalId === approval.approvalId);
3973
+ if (proposal?.runId) {
3974
+ void this.decideProposal(conversation.id, approval, decision, reason);
3975
+ return;
3976
+ }
3977
+ if (conversation.pendingApproval?.approvalId !== approval.approvalId) {
3978
+ // Only the approval the conversation waits for resumes a turn.
2973
3979
  return;
2974
3980
  }
2975
3981
  const conversationId = conversation.id;
@@ -3001,6 +4007,38 @@ class NhAssistantStore {
3001
4007
  }
3002
4008
  });
3003
4009
  }
4010
+ /**
4011
+ * Decides a proposal of an agent's run with `POST approvals/{id}/decisions`. An approved deferred
4012
+ * proposal executes later as a background operation; an inline one wakes the waiting run. The
4013
+ * reloaded snapshot shows the outcome and the proposals that remain open.
4014
+ */
4015
+ async decideProposal(conversationId, approval, decision, reason) {
4016
+ const revision = this.accountRevision;
4017
+ this.patchSession(conversationId, session => ({ ...session, deciding: true, error: null, notice: null }));
4018
+ try {
4019
+ const proposal = await firstValueFrom(this.agentRuns.decide(approval, decision, reason));
4020
+ if (revision === this.accountRevision) {
4021
+ if (decision === 'reject' && proposal.status === 'rejected' && proposal.canGiveFeedback === true) {
4022
+ this.feedbackApprovalsState.update(approvals => new Set(approvals).add(approval.approvalId));
4023
+ }
4024
+ this.patchSession(conversationId, session => ({
4025
+ ...session,
4026
+ conversation: applyProposalDecision(session.conversation, approval.approvalId, proposal.status)
4027
+ }));
4028
+ }
4029
+ }
4030
+ catch (error) {
4031
+ if (revision === this.accountRevision) {
4032
+ this.setError(conversationId, error);
4033
+ }
4034
+ }
4035
+ finally {
4036
+ if (revision === this.accountRevision) {
4037
+ this.patchSession(conversationId, session => ({ ...session, deciding: false }));
4038
+ await this.reloadSession(conversationId);
4039
+ }
4040
+ }
4041
+ }
3004
4042
  /** Asks the server to stop the running turn or to abandon a waiting approval of the open conversation. */
3005
4043
  async cancel() {
3006
4044
  const conversationId = this.activeIdState();
@@ -3333,6 +4371,8 @@ class NhAssistantStore {
3333
4371
  this.queueDispatchState.set(null);
3334
4372
  this.steeringState.set(null);
3335
4373
  this.submittingState.set(false);
4374
+ this.feedbackApprovalsState.set(new Set());
4375
+ this.clearAgentWorkTimers();
3336
4376
  void this.live.stop();
3337
4377
  void this.push.deactivate().catch(() => undefined);
3338
4378
  }
@@ -3537,6 +4577,33 @@ class NhAssistantStore {
3537
4577
  conversation: applyNhAssistantLiveEvent(current.conversation, event)
3538
4578
  }));
3539
4579
  }
4580
+ /**
4581
+ * A run or proposal of an agent thread changed, for example a colleague decided a proposal in the
4582
+ * inbox: reload the thread when this tab has it open, so its approval cards show the decision.
4583
+ * A burst of updates reloads once. Feedback does not change the thread.
4584
+ */
4585
+ onLiveAgentWork(change) {
4586
+ const conversationId = change.conversationId;
4587
+ const session = this.sessionsState().get(conversationId);
4588
+ if (change.reason === 'feedback' || !session || session.streaming) {
4589
+ return;
4590
+ }
4591
+ clearTimeout(this.agentWorkTimers.get(conversationId));
4592
+ this.agentWorkTimers.set(conversationId, setTimeout(() => {
4593
+ this.agentWorkTimers.delete(conversationId);
4594
+ const current = this.sessionsState().get(conversationId);
4595
+ // A decision of this tab reloads the thread itself when it ends.
4596
+ if (current && !current.streaming && !current.deciding) {
4597
+ void this.reloadSession(conversationId);
4598
+ }
4599
+ }, refreshDelayMs));
4600
+ }
4601
+ clearAgentWorkTimers() {
4602
+ for (const timer of this.agentWorkTimers.values()) {
4603
+ clearTimeout(timer);
4604
+ }
4605
+ this.agentWorkTimers.clear();
4606
+ }
3540
4607
  /** After a reconnect: updates sent while disconnected are lost, so reload what is shown. */
3541
4608
  resync() {
3542
4609
  if (!this.enabled()) {
@@ -3688,6 +4755,48 @@ function completionNotice(conversation, errorCode) {
3688
4755
  }
3689
4756
  return null;
3690
4757
  }
4758
+ /** The approvals of a conversation that still wait for a decision, each once. */
4759
+ /** Every approval of the conversation once, by approval id; later parts win. */
4760
+ function approvalParts(conversation) {
4761
+ const approvals = new Map();
4762
+ for (const message of conversation.messages) {
4763
+ for (const part of message.parts) {
4764
+ if (part.type === 'approval') {
4765
+ approvals.set(part.approvalId, part);
4766
+ }
4767
+ }
4768
+ }
4769
+ return [...approvals.values()];
4770
+ }
4771
+ function pendingApprovalParts(conversation) {
4772
+ const approvals = new Map();
4773
+ for (const message of conversation.messages) {
4774
+ for (const part of message.parts) {
4775
+ if (part.type === 'approval' && part.status === 'pending') {
4776
+ approvals.set(part.approvalId, part);
4777
+ }
4778
+ }
4779
+ }
4780
+ if (conversation.pendingApproval?.status === 'pending') {
4781
+ approvals.set(conversation.pendingApproval.approvalId, conversation.pendingApproval);
4782
+ }
4783
+ return [...approvals.values()];
4784
+ }
4785
+ /**
4786
+ * Shows the decision of an agent's proposal until the reloaded snapshot arrives: the approval's new
4787
+ * status, no open decision for it and, for the approval the conversation waited for, no pending one.
4788
+ */
4789
+ function applyProposalDecision(conversation, approvalId, status) {
4790
+ const messages = conversation.messages.map(message => ({
4791
+ ...message,
4792
+ parts: message.parts.map(part => part.type === 'approval' && part.approvalId === approvalId ? { ...part, status } : part)
4793
+ }));
4794
+ const thread = conversation.thread
4795
+ ? { ...conversation.thread, decisions: conversation.thread.decisions.filter(item => item.approvalId !== approvalId) }
4796
+ : conversation.thread;
4797
+ const pendingApproval = conversation.pendingApproval?.approvalId === approvalId ? null : conversation.pendingApproval;
4798
+ return { ...conversation, messages, thread, pendingApproval };
4799
+ }
3691
4800
  function toSummary(conversation) {
3692
4801
  return {
3693
4802
  id: conversation.id,
@@ -3736,6 +4845,7 @@ class NhAssistantPanelService {
3736
4845
  constructor() {
3737
4846
  this.overlay = inject(Overlay);
3738
4847
  this.store = inject(NhAssistantStore);
4848
+ this.agentRuns = inject(NhAssistantAgentRunApiService);
3739
4849
  this.injector = inject(Injector);
3740
4850
  this.document = inject(DOCUMENT);
3741
4851
  this.breakpoints = inject(BreakpointObserver);
@@ -3768,6 +4878,26 @@ class NhAssistantPanelService {
3768
4878
  }
3769
4879
  });
3770
4880
  }
4881
+ /**
4882
+ * Opens the panel on the conversation with an agent about a subject, for example the intake
4883
+ * reviewer about one project. The server returns the agent's thread about the subject and creates
4884
+ * it without starting a run when it is missing; people then type in the conversation, which runs
4885
+ * a turn. Creating needs the participant role, readers may open an existing thread.
4886
+ *
4887
+ * Resolves with the lookup once the panel opens the conversation. A failure, such as
4888
+ * `assistant-subject-access-denied`, rejects with `NhAssistantApiError` and leaves the panel as
4889
+ * it is, so the caller shows the localized `messageKey` where the person asked.
4890
+ */
4891
+ async openSubject(agentId, subject) {
4892
+ const lookup = await firstValueFrom(this.agentRuns.openThread(agentId, subject.type, subject.id));
4893
+ const conversationId = lookup.thread?.conversationId;
4894
+ if (!conversationId) {
4895
+ const code = NhAssistantClientErrorCodes.invalidResponse;
4896
+ throw new NhAssistantApiError(200, code, nhAssistantErrorMessageKey(code));
4897
+ }
4898
+ this.open(conversationId);
4899
+ return lookup;
4900
+ }
3771
4901
  showPanel() {
3772
4902
  if (this.openState()) {
3773
4903
  return;
@@ -3871,6 +5001,324 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
3871
5001
  type: Injectable
3872
5002
  }], ctorParameters: () => [] });
3873
5003
 
5004
+ /** How long a view waits for more agent-work updates before it reloads once. */
5005
+ const NH_ASSISTANT_AGENT_WORK_RELOAD_DELAY_MS = 300;
5006
+ /**
5007
+ * Calls `reload` once a burst of live agent-work updates that `matches` has settled, and after
5008
+ * every (re)connect of the hub, because updates sent while it was disconnected are lost. Without a
5009
+ * live service or a connected hub nothing arrives and the view keeps its manual refresh. Call it in
5010
+ * an injection context, for example a component constructor; it stops when that context ends.
5011
+ */
5012
+ function nhAssistantReloadOnAgentWork(matches, reload) {
5013
+ const live = inject(NhAssistantLiveService, { optional: true });
5014
+ if (!live) {
5015
+ return;
5016
+ }
5017
+ const subscription = merge(live.agentWork$.pipe(filter(matches)), live.resync$)
5018
+ .pipe(debounceTime(NH_ASSISTANT_AGENT_WORK_RELOAD_DELAY_MS))
5019
+ .subscribe(() => reload());
5020
+ inject(DestroyRef).onDestroy(() => subscription.unsubscribe());
5021
+ }
5022
+
5023
+ /** Summaries kept for subjects that nobody shows any more, so a list that re-renders needs no request. */
5024
+ const maxUnwatchedSummaries = 500;
5025
+ /**
5026
+ * Counts of agent work per subject for badges in lists and on pages. Every summary asked for in the
5027
+ * same tick becomes one `POST subjects/{type}/summary` per subject type, in chunks of
5028
+ * `NH_ASSISTANT_MAX_SUBJECT_SUMMARY`. Results are cached per subject and shared by everyone who
5029
+ * shows it. Live agent-work updates refetch only the subjects they name, batched; a reconnect of
5030
+ * the hub refetches every subject shown. Nothing is requested on the server or while the assistant
5031
+ * is disabled or denied, and the cache clears when it becomes unavailable, for example on sign-out.
5032
+ */
5033
+ class NhAssistantSubjectSummaryService {
5034
+ constructor() {
5035
+ this.api = inject(NhAssistantAgentRunApiService);
5036
+ this.store = inject(NhAssistantStore);
5037
+ this.live = inject(NhAssistantLiveService, { optional: true });
5038
+ this.browser = isPlatformBrowser(inject(PLATFORM_ID));
5039
+ this.entries = new Map();
5040
+ /** Subject ids per type that wait for the next request. */
5041
+ this.queue = new Map();
5042
+ /** Subjects named by live updates that wait for the end of the burst. */
5043
+ this.changed = new Set();
5044
+ this.requests = new Subscription();
5045
+ this.liveSubscription = new Subscription();
5046
+ this.flushScheduled = false;
5047
+ this.requestRevision = 0;
5048
+ this.destroyed = false;
5049
+ effect(() => {
5050
+ const enabled = this.store.enabled();
5051
+ untracked(() => this.onAvailabilityChanged(enabled));
5052
+ });
5053
+ if (this.live) {
5054
+ this.liveSubscription.add(this.live.agentWork$.subscribe(change => this.onAgentWork(change)));
5055
+ this.liveSubscription.add(this.live.resync$.subscribe(() => this.refetchShown()));
5056
+ }
5057
+ inject(DestroyRef).onDestroy(() => {
5058
+ this.destroyed = true;
5059
+ this.liveSubscription.unsubscribe();
5060
+ this.requests.unsubscribe();
5061
+ clearTimeout(this.changedTimer);
5062
+ for (const entry of this.entries.values()) {
5063
+ entry.value.complete();
5064
+ }
5065
+ this.entries.clear();
5066
+ this.queue.clear();
5067
+ });
5068
+ }
5069
+ /**
5070
+ * The agent work on one subject: `null` until it is known, then every update. Subscribing shows
5071
+ * the subject; summaries of subjects that are shown stay current with live updates. The request
5072
+ * starts in the next microtask, together with every other summary asked for until then.
5073
+ */
5074
+ summary(subjectType, subjectId) {
5075
+ return new Observable(subscriber => {
5076
+ const entry = this.watch(subjectType, subjectId);
5077
+ const subscription = entry.value.subscribe(subscriber);
5078
+ return () => {
5079
+ subscription.unsubscribe();
5080
+ this.unwatch(entry);
5081
+ };
5082
+ });
5083
+ }
5084
+ /**
5085
+ * Fetches the summaries of these subjects again, or of every subject of the type that is shown,
5086
+ * for example after the application changed them without an agent.
5087
+ */
5088
+ refresh(subjectType, subjectIds) {
5089
+ const ids = subjectIds ? new Set(subjectIds) : null;
5090
+ for (const entry of [...this.entries.values()]) {
5091
+ if (entry.subjectType !== subjectType || (ids && !ids.has(entry.subjectId))) {
5092
+ continue;
5093
+ }
5094
+ if (entry.watchers > 0) {
5095
+ this.enqueue(entry);
5096
+ }
5097
+ else {
5098
+ this.forget(entry);
5099
+ }
5100
+ }
5101
+ }
5102
+ watch(subjectType, subjectId) {
5103
+ const key = summaryKey(subjectType, subjectId);
5104
+ let entry = this.entries.get(key);
5105
+ if (entry) {
5106
+ // The most recently shown summaries stay longest.
5107
+ this.entries.delete(key);
5108
+ }
5109
+ else {
5110
+ entry = {
5111
+ subjectType,
5112
+ subjectId,
5113
+ value: new BehaviorSubject(null),
5114
+ watchers: 0,
5115
+ queued: false,
5116
+ loaded: false,
5117
+ failed: false,
5118
+ request: 0
5119
+ };
5120
+ }
5121
+ this.entries.set(key, entry);
5122
+ entry.watchers++;
5123
+ if (!entry.loaded || entry.failed) {
5124
+ this.enqueue(entry);
5125
+ }
5126
+ if (this.browser) {
5127
+ void this.store.initialize();
5128
+ }
5129
+ return entry;
5130
+ }
5131
+ unwatch(entry) {
5132
+ entry.watchers = Math.max(0, entry.watchers - 1);
5133
+ if (entry.watchers > 0) {
5134
+ return;
5135
+ }
5136
+ let unwatched = 0;
5137
+ for (const item of this.entries.values()) {
5138
+ if (item.watchers === 0) {
5139
+ unwatched++;
5140
+ }
5141
+ }
5142
+ for (const item of [...this.entries.values()]) {
5143
+ if (unwatched <= maxUnwatchedSummaries) {
5144
+ break;
5145
+ }
5146
+ if (item.watchers === 0) {
5147
+ this.forget(item);
5148
+ unwatched--;
5149
+ }
5150
+ }
5151
+ }
5152
+ isCurrent(entry) {
5153
+ return this.entries.get(summaryKey(entry.subjectType, entry.subjectId)) === entry;
5154
+ }
5155
+ /** Drops a summary nobody shows; the next subscriber fetches it again. */
5156
+ forget(entry) {
5157
+ if (this.isCurrent(entry)) {
5158
+ this.entries.delete(summaryKey(entry.subjectType, entry.subjectId));
5159
+ }
5160
+ this.queue.get(entry.subjectType)?.delete(entry.subjectId);
5161
+ entry.value.complete();
5162
+ }
5163
+ enqueue(entry) {
5164
+ if (entry.queued) {
5165
+ return;
5166
+ }
5167
+ entry.queued = true;
5168
+ let ids = this.queue.get(entry.subjectType);
5169
+ if (!ids) {
5170
+ ids = new Set();
5171
+ this.queue.set(entry.subjectType, ids);
5172
+ }
5173
+ ids.add(entry.subjectId);
5174
+ this.scheduleFlush();
5175
+ }
5176
+ scheduleFlush() {
5177
+ if (this.flushScheduled) {
5178
+ return;
5179
+ }
5180
+ this.flushScheduled = true;
5181
+ queueMicrotask(() => {
5182
+ this.flushScheduled = false;
5183
+ this.flush();
5184
+ });
5185
+ }
5186
+ /** Sends the queued subjects: one request per type and chunk. Waits while the assistant is unavailable. */
5187
+ flush() {
5188
+ if (this.destroyed || !this.browser || !this.store.enabled()) {
5189
+ return;
5190
+ }
5191
+ for (const [subjectType, ids] of [...this.queue]) {
5192
+ this.queue.delete(subjectType);
5193
+ const subjectIds = [...ids];
5194
+ for (let start = 0; start < subjectIds.length; start += NH_ASSISTANT_MAX_SUBJECT_SUMMARY) {
5195
+ this.send(subjectType, subjectIds.slice(start, start + NH_ASSISTANT_MAX_SUBJECT_SUMMARY));
5196
+ }
5197
+ }
5198
+ }
5199
+ send(subjectType, subjectIds) {
5200
+ const revision = ++this.requestRevision;
5201
+ const entries = [];
5202
+ for (const subjectId of subjectIds) {
5203
+ const entry = this.entries.get(summaryKey(subjectType, subjectId));
5204
+ if (entry) {
5205
+ entry.queued = false;
5206
+ entry.request = revision;
5207
+ entries.push(entry);
5208
+ }
5209
+ }
5210
+ if (entries.length === 0) {
5211
+ return;
5212
+ }
5213
+ const subscription = this.api.summarizeSubjects(subjectType, entries.map(entry => entry.subjectId)).subscribe({
5214
+ next: list => {
5215
+ const items = new Map(list.items.map(item => [item.subjectId, item]));
5216
+ for (const entry of entries) {
5217
+ if (entry.request !== revision || !this.isCurrent(entry)) {
5218
+ continue;
5219
+ }
5220
+ entry.loaded = true;
5221
+ entry.failed = false;
5222
+ entry.value.next(items.get(entry.subjectId) ?? emptySummary(entry.subjectId));
5223
+ }
5224
+ },
5225
+ error: () => {
5226
+ // A badge without a count is better than an error in a list; the next live update,
5227
+ // reconnect or subscriber tries again.
5228
+ for (const entry of entries) {
5229
+ if (entry.request === revision) {
5230
+ entry.failed = true;
5231
+ }
5232
+ }
5233
+ }
5234
+ });
5235
+ this.requests.add(subscription);
5236
+ }
5237
+ onAgentWork(change) {
5238
+ if (!change.subjectType || !change.subjectId) {
5239
+ return;
5240
+ }
5241
+ const key = summaryKey(change.subjectType, change.subjectId);
5242
+ const entry = this.entries.get(key);
5243
+ if (!entry) {
5244
+ return;
5245
+ }
5246
+ if (entry.watchers === 0) {
5247
+ this.forget(entry);
5248
+ return;
5249
+ }
5250
+ this.changed.add(key);
5251
+ if (this.changedTimer === undefined) {
5252
+ this.changedTimer = setTimeout(() => this.refetchChanged(), NH_ASSISTANT_AGENT_WORK_RELOAD_DELAY_MS);
5253
+ }
5254
+ }
5255
+ /** Refetches the shown subjects of a burst of live updates, together. */
5256
+ refetchChanged() {
5257
+ this.changedTimer = undefined;
5258
+ const keys = [...this.changed];
5259
+ this.changed.clear();
5260
+ for (const key of keys) {
5261
+ const entry = this.entries.get(key);
5262
+ if (entry && entry.watchers > 0) {
5263
+ this.enqueue(entry);
5264
+ }
5265
+ }
5266
+ }
5267
+ /** After a reconnect updates may have been missed: refetch what is shown, forget the rest. */
5268
+ refetchShown() {
5269
+ for (const entry of [...this.entries.values()]) {
5270
+ if (entry.watchers > 0) {
5271
+ this.enqueue(entry);
5272
+ }
5273
+ else {
5274
+ this.forget(entry);
5275
+ }
5276
+ }
5277
+ }
5278
+ onAvailabilityChanged(enabled) {
5279
+ if (enabled) {
5280
+ for (const entry of this.entries.values()) {
5281
+ if (entry.watchers > 0 && (!entry.loaded || entry.failed)) {
5282
+ this.enqueue(entry);
5283
+ }
5284
+ }
5285
+ this.scheduleFlush();
5286
+ return;
5287
+ }
5288
+ // Signed out, denied or disabled: no counts of a previous account may remain.
5289
+ this.requests.unsubscribe();
5290
+ this.requests = new Subscription();
5291
+ clearTimeout(this.changedTimer);
5292
+ this.changedTimer = undefined;
5293
+ this.changed.clear();
5294
+ this.queue.clear();
5295
+ for (const entry of [...this.entries.values()]) {
5296
+ if (entry.watchers === 0) {
5297
+ this.forget(entry);
5298
+ continue;
5299
+ }
5300
+ entry.queued = false;
5301
+ entry.loaded = false;
5302
+ entry.failed = false;
5303
+ entry.request = 0;
5304
+ if (entry.value.value !== null) {
5305
+ entry.value.next(null);
5306
+ }
5307
+ }
5308
+ }
5309
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantSubjectSummaryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5310
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantSubjectSummaryService }); }
5311
+ }
5312
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantSubjectSummaryService, decorators: [{
5313
+ type: Injectable
5314
+ }], ctorParameters: () => [] });
5315
+ function summaryKey(subjectType, subjectId) {
5316
+ return `${subjectType}\u0000${subjectId}`;
5317
+ }
5318
+ function emptySummary(subjectId) {
5319
+ return { subjectId, openProposals: 0, proposalsToDecide: 0, runActive: false };
5320
+ }
5321
+
3874
5322
  /**
3875
5323
  * Registers the assistant client, store and panel for one application or route scope.
3876
5324
  * Call it once in the root providers; a route may call it again to host an isolated
@@ -3883,11 +5331,13 @@ function provideNhAssistant(config) {
3883
5331
  NhAssistantTransport,
3884
5332
  NhAssistantApiService,
3885
5333
  NhAssistantAdminApiService,
5334
+ NhAssistantAgentRunApiService,
3886
5335
  NhAssistantLiveService,
3887
5336
  NhAssistantPushService,
3888
5337
  NhAssistantStore,
3889
5338
  NhAssistantUiState,
3890
5339
  NhAssistantPanelService,
5340
+ NhAssistantSubjectSummaryService,
3891
5341
  ...(config.translations === 'host'
3892
5342
  ? []
3893
5343
  : [provideEnvironmentInitializer(() => inject(NhAssistantTranslationMerger).start())])
@@ -4189,7 +5639,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
4189
5639
  `, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0}.strip{display:flex;gap:6px;overflow-x:auto;padding:0 12px 8px 16px;scrollbar-width:none}.strip::-webkit-scrollbar{display:none}.chip{display:inline-flex;flex:none;align-items:center;gap:6px;max-width:180px;height:26px;border:1px solid var(--nha-border);border-radius:999px;padding:0 10px 0 8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;font-size:12px;cursor:pointer}.chip:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip:hover{border-color:var(--nha-highlight)}.title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shared{flex:none;color:var(--nha-text-muted);font-size:12px}.indicator{flex:none;width:8px;height:8px;border-radius:50%;background:var(--nha-accent)}.chip[data-state=unread] .indicator{background:var(--nha-highlight)}.chip[data-state=waiting] .indicator{background:var(--nha-warning)}.chip[data-state=running] .indicator{width:10px;height:10px;border:2px solid color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);background:transparent;animation:nh-assistant-spin .9s linear infinite}@media(prefers-reduced-motion:reduce){.chip[data-state=running] .indicator{border-color:var(--nha-highlight);animation:none}}@keyframes nh-assistant-spin{to{transform:rotate(360deg)}}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
4190
5640
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], conversationSelect: [{ type: i0.Output, args: ["conversationSelect"] }] } });
4191
5641
 
4192
- let nextId$6 = 0;
5642
+ let nextId$8 = 0;
4193
5643
  /**
4194
5644
  * Chooses the agent for new conversations. Agent names come from the host's translation
4195
5645
  * keys; agents created by an administrator carry literal text, which is shown as is.
@@ -4202,7 +5652,7 @@ class NhAssistantAgentPickerComponent {
4202
5652
  /** Compact prompt-bar control; capability information remains in the tooltip and accessible description. */
4203
5653
  this.compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : []));
4204
5654
  this.agentChange = output();
4205
- this.selectId = `nh-assistant-agent-picker-${nextId$6++}`;
5655
+ this.selectId = `nh-assistant-agent-picker-${nextId$8++}`;
4206
5656
  this.selected = computed(() => this.agents().find(agent => agent.id === this.selectedAgentId()) ?? null, ...(ngDevMode ? [{ debugName: "selected" }] : []));
4207
5657
  }
4208
5658
  onChange(event) {
@@ -4254,7 +5704,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
4254
5704
  `, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;gap:2px;min-width:0}:host(.compact){flex-direction:row;align-items:center}:host(.compact) select{min-width:0;max-width:100%;border-color:transparent;padding-left:8px;background:transparent;cursor:pointer}:host(.compact) select:hover:not(:disabled){border-color:var(--nha-border);background:var(--nha-surface-strong)}:host(.compact) select:disabled{cursor:default}select{align-self:flex-start;max-width:100%;min-height:32px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:4px 28px 4px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px;font-weight:650}select:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}select:disabled{opacity:1;cursor:default}.capability{padding-left:2px;color:var(--nha-text-muted);font-size:11px}.capability[data-can-mutate=true]{color:var(--nha-warning)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
4255
5705
  }], propDecorators: { agents: [{ type: i0.Input, args: [{ isSignal: true, alias: "agents", required: true }] }], selectedAgentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedAgentId", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], agentChange: [{ type: i0.Output, args: ["agentChange"] }] } });
4256
5706
 
4257
- let nextId$5 = 0;
5707
+ let nextId$7 = 0;
4258
5708
  /**
4259
5709
  * Message input. Enter sends when sending is available; Shift+Enter inserts a new line.
4260
5710
  * Drafting remains available while the assistant runs or waits for approval.
@@ -4285,7 +5735,7 @@ class NhAssistantComposerComponent {
4285
5735
  this.textarea = viewChild.required('textarea');
4286
5736
  this.ownedFocus = false;
4287
5737
  this.previousBusy = false;
4288
- this.inputId = `nh-assistant-composer-${nextId$5++}`;
5738
+ this.inputId = `nh-assistant-composer-${nextId$7++}`;
4289
5739
  this.hintId = `${this.inputId}-hint`;
4290
5740
  this.text = signal('', ...(ngDevMode ? [{ debugName: "text" }] : []));
4291
5741
  this.length = computed(() => this.text().trim().length, ...(ngDevMode ? [{ debugName: "length" }] : []));
@@ -4409,7 +5859,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
4409
5859
 
4410
5860
  /** Maximum length of the user's own instructions. */
4411
5861
  const NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS = 1_000;
4412
- let nextId$4 = 0;
5862
+ let nextId$6 = 0;
4413
5863
  /**
4414
5864
  * The user's own assistant preferences: desktop notifications, style, form of address,
4415
5865
  * answer length and optional instructions. Notifications apply immediately; the other
@@ -4424,7 +5874,7 @@ class NhAssistantPreferencesComponent {
4424
5874
  this.closed = output();
4425
5875
  /** Emitted with the stored preferences after a successful save. */
4426
5876
  this.saved = output();
4427
- this.id = `nh-assistant-preferences-${nextId$4++}`;
5877
+ this.id = `nh-assistant-preferences-${nextId$6++}`;
4428
5878
  this.styles = ['default', 'direct', 'personal', 'detailed'];
4429
5879
  this.addressForms = ['informal', 'formal'];
4430
5880
  this.responseLengths = ['short', 'normal', 'long'];
@@ -4535,7 +5985,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
4535
5985
  args: [{ selector: 'nh-assistant-preferences', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"preferences\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || saving()\">\n <header class=\"header\">\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.preferences.title' | translate }}</h3>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.preferences.back' | translate\"\n [attr.title]=\"'nh-assistant.preferences.back' | translate\"\n (click)=\"closed.emit()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </header>\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.preferences.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.preferences.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.preferences.retry' | translate }}</button>\n </div>\n } @else {\n <form class=\"form\" (submit)=\"$event.preventDefault(); save()\">\n @if (push.state() !== 'unavailable') {\n <fieldset class=\"notifications\">\n <legend>{{ 'nh-assistant.preferences.notifications-label' | translate }}</legend>\n <label class=\"switch\">\n <input\n type=\"checkbox\"\n [checked]=\"push.settings()?.pushEnabled === true\"\n [disabled]=\"push.busy()\"\n [attr.aria-describedby]=\"id + '-notifications-hint'\"\n (change)=\"toggleNotifications($event)\" />\n <span>{{ 'nh-assistant.preferences.notifications' | translate }}</span>\n </label>\n <p class=\"hint\" [id]=\"id + '-notifications-hint'\">\n <span>{{ 'nh-assistant.preferences.notifications-state.' + push.state() | translate }}</span>\n </p>\n </fieldset>\n }\n\n <p class=\"description\">{{ 'nh-assistant.preferences.description' | translate }}</p>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.style-label' | translate }}</legend>\n <div class=\"options styles\">\n @for (option of styles; track option) {\n <label class=\"option\" [class.selected]=\"style() === option\">\n <input type=\"radio\" [name]=\"id + '-style'\" [value]=\"option\" [checked]=\"style() === option\" (change)=\"choose(style, option)\" />\n <span>\n <strong>{{ 'nh-assistant.preferences.style.' + option | translate }}</strong>\n <small>{{ 'nh-assistant.preferences.style.' + option + '-description' | translate }}</small>\n </span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.address-form-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of addressForms; track option) {\n <label class=\"option\" [class.selected]=\"addressForm() === option\">\n <input type=\"radio\" [name]=\"id + '-address'\" [value]=\"option\" [checked]=\"addressForm() === option\" (change)=\"choose(addressForm, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.address-form.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.response-length-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of responseLengths; track option) {\n <label class=\"option\" [class.selected]=\"responseLength() === option\">\n <input type=\"radio\" [name]=\"id + '-length'\" [value]=\"option\" [checked]=\"responseLength() === option\" (change)=\"choose(responseLength, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.response-length.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.preferences.custom-instructions-label' | translate }}</label>\n <textarea\n rows=\"4\"\n [id]=\"id + '-instructions'\"\n [value]=\"customInstructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInstructionsInput($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n @if (tooLong()) {\n <span class=\"error-text\">{{ 'nh-assistant.preferences.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.preferences.custom-instructions-hint' | translate }}</span>\n }\n <span class=\"counter\" [class.error-text]=\"tooLong()\">\n {{ 'nh-assistant.preferences.characters' | translate: { count: instructionsLength(), max: maxInstructions } }}\n </span>\n </p>\n </div>\n\n @if (error(); as error) {\n <p class=\"message error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n {{ errorKeys() | nhAssistantTranslate: error.code }}\n </p>\n }\n @if (savedMessage()) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.preferences.saved' | translate }}\n </p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"closed.emit()\">{{ 'nh-assistant.preferences.back' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSave()\">\n {{ (saving() ? 'nh-assistant.preferences.saving' : 'nh-assistant.preferences.save') | translate }}\n </button>\n </div>\n </form>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.preferences{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:15px;font-weight:700}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.form{display:flex;flex:1;flex-direction:column;gap:18px;min-height:0;overflow-y:auto;padding:0 16px 16px}.description{margin:0;color:var(--nha-text-muted);font-size:13px}.options{display:grid;gap:8px}.styles{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.segmented{grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}.option{position:relative;display:flex;align-items:flex-start;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px;background:var(--nha-background);cursor:pointer}.option:hover{background:var(--nha-surface)}.option.selected{border-color:var(--nha-highlight);background:var(--nha-surface);box-shadow:inset 0 0 0 1px var(--nha-highlight)}.option:focus-within{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.option input{flex:none;margin:2px 0 0;accent-color:var(--nha-highlight)}.option span{display:flex;flex-direction:column;gap:2px;min-width:0}.option strong{font-size:13px}.option small{color:var(--nha-text-muted);font-size:12px}.switch{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:650;cursor:pointer}.switch input{width:16px;height:16px;margin:0;accent-color:var(--nha-highlight)}\n"] }]
4536
5986
  }], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], saved: [{ type: i0.Output, args: ["saved"] }] } });
4537
5987
 
4538
- let nextId$3 = 0;
5988
+ let nextId$5 = 0;
4539
5989
  const searchDelayMs = 250;
4540
5990
  /**
4541
5991
  * Sharing of the open conversation. The owner creates, copies, renews or stops an invitation
@@ -4551,7 +6001,7 @@ class NhAssistantShareComponent {
4551
6001
  this.closed = output();
4552
6002
  /** Show the invitation link, participants, or both (the default for custom hosts). */
4553
6003
  this.section = input('all', ...(ngDevMode ? [{ debugName: "section" }] : []));
4554
- this.id = `nh-assistant-share-${nextId$3++}`;
6004
+ this.id = `nh-assistant-share-${nextId$5++}`;
4555
6005
  this.conversation = this.store.activeConversation;
4556
6006
  this.isOwner = this.store.isOwner;
4557
6007
  this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
@@ -4796,6 +6246,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
4796
6246
  args: [{ name: 'nhAssistantPartGroups', standalone: true }]
4797
6247
  }] });
4798
6248
 
6249
+ /** Converts any failure into a user-facing code and translation key, never raw text. */
6250
+ function toNhAssistantError(error) {
6251
+ if (error instanceof NhAssistantApiError) {
6252
+ return { code: error.code, messageKey: error.messageKey };
6253
+ }
6254
+ return { code: NhAssistantClientErrorCodes.server, messageKey: nhAssistantErrorMessageKey(NhAssistantClientErrorCodes.server) };
6255
+ }
6256
+ /** Translation keys for an error, from the most to the least specific. */
6257
+ function nhAssistantErrorKeys(error) {
6258
+ return error ? [error.messageKey, nhAssistantErrorMessageKey(error.code), 'nh-assistant.errors.generic'] : [];
6259
+ }
6260
+ /** Dash-case identifiers as the contract requires for agent and MCP server ids. */
6261
+ const NH_ASSISTANT_DASH_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6262
+
6263
+ const notAcceptedCode = 'assistant-feedback-not-accepted';
6264
+ let nextId$4 = 0;
6265
+ /**
6266
+ * Asks, after a rejection, what was wrong with one or more agent proposals: a category from a fixed
6267
+ * set and an optional text. It is optional and never blocks the decision; people skip it with one
6268
+ * action. One proposal is sent with `PUT approvals/{id}/feedback`, several rejected together with
6269
+ * `POST approvals/feedback` as bulk feedback. With `initial` it changes the caller's own feedback.
6270
+ * After saving it thanks the person in place of the form; hosts remove it after `dismissed`.
6271
+ */
6272
+ class NhAssistantProposalFeedbackComponent {
6273
+ constructor() {
6274
+ this.api = inject(NhAssistantAgentRunApiService);
6275
+ this.host = inject(ElementRef);
6276
+ this.document = inject(DOCUMENT);
6277
+ this.injector = inject(Injector);
6278
+ this.thanks = viewChild('thanks', ...(ngDevMode ? [{ debugName: "thanks" }] : []));
6279
+ /** The proposals the feedback is about; several when they were rejected together. */
6280
+ this.approvalIds = input.required(...(ngDevMode ? [{ debugName: "approvalIds" }] : []));
6281
+ /** The caller's current feedback, to change it; `null` for new feedback. */
6282
+ this.initial = input(null, ...(ngDevMode ? [{ debugName: "initial" }] : []));
6283
+ /** Emits after the server stored the feedback. */
6284
+ this.saved = output();
6285
+ /** Emits when the person skips the question. */
6286
+ this.dismissed = output();
6287
+ this.id = `nh-assistant-proposal-feedback-${nextId$4++}`;
6288
+ this.categories = NH_ASSISTANT_FEEDBACK_CATEGORIES;
6289
+ this.maxText = NH_ASSISTANT_MAX_FEEDBACK_TEXT;
6290
+ /** A new question when the host passes other proposals or other initial feedback. */
6291
+ this.question = computed(() => ({ approvalIds: this.approvalIds(), initial: this.initial() }), ...(ngDevMode ? [{ debugName: "question" }] : []));
6292
+ this.category = linkedSignal(() => this.question().initial?.category ?? null, ...(ngDevMode ? [{ debugName: "category" }] : []));
6293
+ this.text = linkedSignal(() => this.question().initial?.text ?? '', ...(ngDevMode ? [{ debugName: "text" }] : []));
6294
+ this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
6295
+ this.done = linkedSignal(...(ngDevMode ? [{ debugName: "done", source: this.question, computation: () => false }] : [{ source: this.question, computation: () => false }]));
6296
+ /** Set when a bulk request stored the feedback for only part of the proposals. */
6297
+ this.partial = linkedSignal(...(ngDevMode ? [{ debugName: "partial", source: this.question,
6298
+ computation: () => null }] : [{
6299
+ source: this.question,
6300
+ computation: () => null
6301
+ }]));
6302
+ this.error = linkedSignal(...(ngDevMode ? [{ debugName: "error", source: this.question, computation: () => null }] : [{ source: this.question, computation: () => null }]));
6303
+ this.errorKeys = computed(() => nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
6304
+ this.count = computed(() => new Set(this.approvalIds()).size, ...(ngDevMode ? [{ debugName: "count" }] : []));
6305
+ this.textLength = computed(() => this.text().length, ...(ngDevMode ? [{ debugName: "textLength" }] : []));
6306
+ this.tooLong = computed(() => this.textLength() > this.maxText, ...(ngDevMode ? [{ debugName: "tooLong" }] : []));
6307
+ this.canSend = computed(() => this.category() !== null && this.count() > 0 && !this.busy() && !this.tooLong(), ...(ngDevMode ? [{ debugName: "canSend" }] : []));
6308
+ inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
6309
+ }
6310
+ choose(category) {
6311
+ this.category.set(category);
6312
+ this.error.set(null);
6313
+ }
6314
+ onText(event) {
6315
+ this.text.set(event.target.value);
6316
+ }
6317
+ dismiss() {
6318
+ this.subscription?.unsubscribe();
6319
+ this.busy.set(false);
6320
+ this.dismissed.emit();
6321
+ }
6322
+ send() {
6323
+ const category = this.category();
6324
+ if (!this.canSend() || category === null) {
6325
+ return;
6326
+ }
6327
+ const approvalIds = [...new Set(this.approvalIds())];
6328
+ const text = this.text().trim() || null;
6329
+ this.busy.set(true);
6330
+ this.error.set(null);
6331
+ this.subscription?.unsubscribe();
6332
+ this.subscription = this.request(approvalIds, category, text).subscribe({
6333
+ next: outcomes => {
6334
+ this.busy.set(false);
6335
+ const stored = outcomes.filter(outcome => outcome.succeeded).map(outcome => outcome.approvalId);
6336
+ if (stored.length === 0) {
6337
+ const code = outcomes.find(outcome => outcome.code)?.code ?? notAcceptedCode;
6338
+ this.error.set({ code, messageKey: nhAssistantErrorMessageKey(code) });
6339
+ return;
6340
+ }
6341
+ // The form disappears; keep keyboard focus on the confirmation instead of losing it.
6342
+ const focused = this.host.nativeElement.contains(this.document.activeElement);
6343
+ this.partial.set(stored.length < approvalIds.length ? { stored: stored.length, total: approvalIds.length } : null);
6344
+ this.done.set(true);
6345
+ if (focused) {
6346
+ afterNextRender(() => this.thanks()?.nativeElement.focus(), { injector: this.injector });
6347
+ }
6348
+ this.saved.emit({ approvalIds: stored, category, text });
6349
+ },
6350
+ error: failure => {
6351
+ this.busy.set(false);
6352
+ this.error.set(toNhAssistantError(failure));
6353
+ }
6354
+ });
6355
+ }
6356
+ /** One proposal through its own endpoint; several in requests of at most the bulk limit. */
6357
+ request(approvalIds, category, text) {
6358
+ if (approvalIds.length === 1) {
6359
+ const [approvalId] = approvalIds;
6360
+ return this.api.giveFeedback(approvalId, category, text).pipe(map(() => [{ approvalId, succeeded: true, code: null }]));
6361
+ }
6362
+ const chunks = [];
6363
+ for (let index = 0; index < approvalIds.length; index += NH_ASSISTANT_MAX_BULK_FEEDBACK) {
6364
+ chunks.push(approvalIds.slice(index, index + NH_ASSISTANT_MAX_BULK_FEEDBACK));
6365
+ }
6366
+ return forkJoin(chunks.map(chunk => this.api.giveFeedbackMany(chunk, category, text))).pipe(map(responses => responses.flatMap(response => response.results)));
6367
+ }
6368
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantProposalFeedbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6369
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantProposalFeedbackComponent, isStandalone: true, selector: "nh-assistant-proposal-feedback", inputs: { approvalIds: { classPropertyName: "approvalIds", publicName: "approvalIds", isSignal: true, isRequired: true, transformFunction: null }, initial: { classPropertyName: "initial", publicName: "initial", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", dismissed: "dismissed" }, viewQueries: [{ propertyName: "thanks", first: true, predicate: ["thanks"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (done()) {\n <div class=\"feedback done\" role=\"status\" tabindex=\"-1\" #thanks>\n <p class=\"thanks\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.proposal-feedback.thanks' | translate }}</p>\n @if (partial(); as stored) {\n <p class=\"scope\">{{ 'nh-assistant.proposal-feedback.partial' | translate: { stored: stored.stored, total: stored.total } }}</p>\n }\n </div>\n} @else {\n <form class=\"feedback\" [attr.aria-busy]=\"busy()\" (submit)=\"$event.preventDefault(); send()\">\n <fieldset class=\"categories\" [disabled]=\"busy()\">\n <legend>{{ 'nh-assistant.proposal-feedback.legend' | translate }}</legend>\n @if (count() > 1) {\n <p class=\"scope\">{{ 'nh-assistant.proposal-feedback.bulk' | translate: { count: count() } }}</p>\n }\n <div class=\"chips\">\n @for (option of categories; track option) {\n <label class=\"chip\" [class.selected]=\"category() === option\">\n <input\n type=\"radio\"\n [name]=\"id + '-category'\"\n [value]=\"option\"\n [checked]=\"category() === option\"\n [attr.aria-describedby]=\"id + '-hint-' + option\"\n (change)=\"choose(option)\" />\n @if (category() === option) {\n <nh-assistant-icon name=\"check\" />\n }\n <span>{{ 'nh-assistant.proposal-feedback.categories.' + option | translate }}</span>\n </label>\n }\n </div>\n @for (option of categories; track option) {\n <span class=\"visually-hidden\" [id]=\"id + '-hint-' + option\">{{ 'nh-assistant.proposal-feedback.hints.' + option | translate }}</span>\n }\n @if (category(); as selected) {\n <p class=\"selected-hint\" aria-hidden=\"true\">{{ 'nh-assistant.proposal-feedback.hints.' + selected | translate }}</p>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-text'\">{{ 'nh-assistant.proposal-feedback.text-label' | translate }}</label>\n <textarea\n rows=\"2\"\n [id]=\"id + '-text'\"\n [attr.maxlength]=\"maxText\"\n [value]=\"text()\"\n [attr.aria-describedby]=\"id + '-text-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onText($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-text-hint'\">\n <span>{{ 'nh-assistant.proposal-feedback.text-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">\n {{ 'nh-assistant.proposal-feedback.characters' | translate: { count: textLength(), max: maxText } }}\n </span>\n </p>\n </div>\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"text-button\" type=\"button\" (click)=\"dismiss()\">\n {{ (initial() ? 'nh-assistant.proposal-feedback.cancel' : 'nh-assistant.proposal-feedback.skip') | translate }}\n </button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSend()\">\n {{ (busy() ? 'nh-assistant.proposal-feedback.sending' : 'nh-assistant.proposal-feedback.send') | translate }}\n </button>\n </div>\n </form>\n}\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:13px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.feedback{display:flex;flex-direction:column;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;background:var(--nha-surface)}.feedback.done{gap:4px}.feedback.done:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.categories{display:flex;flex-direction:column;gap:8px}.chips{display:flex;flex-wrap:wrap;gap:6px}.chip{position:relative;display:inline-flex;align-items:center;gap:4px;min-height:30px;max-width:100%;border:1px solid var(--nha-border);border-radius:999px;padding:4px 12px;color:var(--nha-text);background:var(--nha-background);font-size:12px;font-weight:650;overflow-wrap:anywhere;cursor:pointer}.chip:hover{background:var(--nha-surface-strong)}.chip.selected{border-color:var(--nha-highlight);box-shadow:inset 0 0 0 1px var(--nha-highlight)}.chip:has(input:focus-visible){outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.chip nh-assistant-icon{color:var(--nha-highlight)}fieldset:disabled .chip{cursor:not-allowed;opacity:.6}.scope,.selected-hint{margin:0;color:var(--nha-text-muted);font-size:12px}.thanks{display:flex;align-items:center;gap:6px;margin:0;font-size:13px}.thanks nh-assistant-icon{color:var(--nha-success)}textarea{min-height:56px}.text-button{min-height:36px;border:0;border-radius:var(--nha-radius-control);padding:6px 10px;color:var(--nha-text-muted);background:transparent;font:inherit;font-size:13px;font-weight:650;cursor:pointer}.text-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.text-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.text-button:disabled{cursor:not-allowed;opacity:.55}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6370
+ }
6371
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantProposalFeedbackComponent, decorators: [{
6372
+ type: Component,
6373
+ args: [{ selector: 'nh-assistant-proposal-feedback', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (done()) {\n <div class=\"feedback done\" role=\"status\" tabindex=\"-1\" #thanks>\n <p class=\"thanks\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.proposal-feedback.thanks' | translate }}</p>\n @if (partial(); as stored) {\n <p class=\"scope\">{{ 'nh-assistant.proposal-feedback.partial' | translate: { stored: stored.stored, total: stored.total } }}</p>\n }\n </div>\n} @else {\n <form class=\"feedback\" [attr.aria-busy]=\"busy()\" (submit)=\"$event.preventDefault(); send()\">\n <fieldset class=\"categories\" [disabled]=\"busy()\">\n <legend>{{ 'nh-assistant.proposal-feedback.legend' | translate }}</legend>\n @if (count() > 1) {\n <p class=\"scope\">{{ 'nh-assistant.proposal-feedback.bulk' | translate: { count: count() } }}</p>\n }\n <div class=\"chips\">\n @for (option of categories; track option) {\n <label class=\"chip\" [class.selected]=\"category() === option\">\n <input\n type=\"radio\"\n [name]=\"id + '-category'\"\n [value]=\"option\"\n [checked]=\"category() === option\"\n [attr.aria-describedby]=\"id + '-hint-' + option\"\n (change)=\"choose(option)\" />\n @if (category() === option) {\n <nh-assistant-icon name=\"check\" />\n }\n <span>{{ 'nh-assistant.proposal-feedback.categories.' + option | translate }}</span>\n </label>\n }\n </div>\n @for (option of categories; track option) {\n <span class=\"visually-hidden\" [id]=\"id + '-hint-' + option\">{{ 'nh-assistant.proposal-feedback.hints.' + option | translate }}</span>\n }\n @if (category(); as selected) {\n <p class=\"selected-hint\" aria-hidden=\"true\">{{ 'nh-assistant.proposal-feedback.hints.' + selected | translate }}</p>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-text'\">{{ 'nh-assistant.proposal-feedback.text-label' | translate }}</label>\n <textarea\n rows=\"2\"\n [id]=\"id + '-text'\"\n [attr.maxlength]=\"maxText\"\n [value]=\"text()\"\n [attr.aria-describedby]=\"id + '-text-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onText($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-text-hint'\">\n <span>{{ 'nh-assistant.proposal-feedback.text-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">\n {{ 'nh-assistant.proposal-feedback.characters' | translate: { count: textLength(), max: maxText } }}\n </span>\n </p>\n </div>\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"text-button\" type=\"button\" (click)=\"dismiss()\">\n {{ (initial() ? 'nh-assistant.proposal-feedback.cancel' : 'nh-assistant.proposal-feedback.skip') | translate }}\n </button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSend()\">\n {{ (busy() ? 'nh-assistant.proposal-feedback.sending' : 'nh-assistant.proposal-feedback.send') | translate }}\n </button>\n </div>\n </form>\n}\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:13px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.feedback{display:flex;flex-direction:column;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;background:var(--nha-surface)}.feedback.done{gap:4px}.feedback.done:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.categories{display:flex;flex-direction:column;gap:8px}.chips{display:flex;flex-wrap:wrap;gap:6px}.chip{position:relative;display:inline-flex;align-items:center;gap:4px;min-height:30px;max-width:100%;border:1px solid var(--nha-border);border-radius:999px;padding:4px 12px;color:var(--nha-text);background:var(--nha-background);font-size:12px;font-weight:650;overflow-wrap:anywhere;cursor:pointer}.chip:hover{background:var(--nha-surface-strong)}.chip.selected{border-color:var(--nha-highlight);box-shadow:inset 0 0 0 1px var(--nha-highlight)}.chip:has(input:focus-visible){outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.chip nh-assistant-icon{color:var(--nha-highlight)}fieldset:disabled .chip{cursor:not-allowed;opacity:.6}.scope,.selected-hint{margin:0;color:var(--nha-text-muted);font-size:12px}.thanks{display:flex;align-items:center;gap:6px;margin:0;font-size:13px}.thanks nh-assistant-icon{color:var(--nha-success)}textarea{min-height:56px}.text-button{min-height:36px;border:0;border-radius:var(--nha-radius-control);padding:6px 10px;color:var(--nha-text-muted);background:transparent;font:inherit;font-size:13px;font-weight:650;cursor:pointer}.text-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.text-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.text-button:disabled{cursor:not-allowed;opacity:.55}\n"] }]
6374
+ }], ctorParameters: () => [], propDecorators: { thanks: [{ type: i0.ViewChild, args: ['thanks', { isSignal: true }] }], approvalIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "approvalIds", required: true }] }], initial: [{ type: i0.Input, args: [{ isSignal: true, alias: "initial", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }] } });
6375
+
6376
+ let nextId$3 = 0;
4799
6377
  /** Formats remaining milliseconds as `m:ss`, or `h:mm:ss` above one hour. */
4800
6378
  function formatNhAssistantCountdown(milliseconds) {
4801
6379
  const totalSeconds = Math.max(0, Math.ceil(milliseconds / 1000));
@@ -4810,7 +6388,9 @@ function formatNhAssistantCountdown(milliseconds) {
4810
6388
  /**
4811
6389
  * Asks the user to approve or reject a proposed mutation. Shows the summary, targets,
4812
6390
  * proposed arguments and a countdown to expiry. A decision is emitted at most once until
4813
- * the host reports that it is no longer deciding.
6391
+ * the host reports that it is no longer deciding. After the user rejected an agent's proposal
6392
+ * (`canGiveFeedback`), a low-key action asks what was wrong. A proposal that closed because it no
6393
+ * longer fits shows why, such as "Already done", with the application's explanation.
4814
6394
  */
4815
6395
  class NhAssistantApprovalCardComponent {
4816
6396
  constructor() {
@@ -4818,23 +6398,71 @@ class NhAssistantApprovalCardComponent {
4818
6398
  /** True while the host submits a decision; disables both buttons. */
4819
6399
  this.deciding = input(false, ...(ngDevMode ? [{ debugName: "deciding" }] : []));
4820
6400
  /**
4821
- * False when the approval belongs to another participant's turn: only the person whose
4822
- * permissions the action would use may decide it.
6401
+ * False when the current user may not decide: in a chat the approval belongs to another
6402
+ * participant's turn, in an agent thread the user is no approver of the proposal.
4823
6403
  */
4824
6404
  this.canDecide = input(true, ...(ngDevMode ? [{ debugName: "canDecide" }] : []));
4825
- /** Name of the participant who decides, shown when the current user cannot. */
6405
+ /** Name of the participant who decides a `turn` approval, shown when the current user cannot. */
4826
6406
  this.decisionOwner = input(null, ...(ngDevMode ? [{ debugName: "decisionOwner" }] : []));
6407
+ /**
6408
+ * Who decides when the current user cannot: the author of the turn (`turn`, the default), an
6409
+ * approver of the agent thread's subject (`approver`), or another approver because the current
6410
+ * user started the work (`other-approver`).
6411
+ */
6412
+ this.waitingFor = input('turn', ...(ngDevMode ? [{ debugName: "waitingFor" }] : []));
6413
+ /**
6414
+ * True when the current user rejected this agent proposal and may say what was wrong, for example
6415
+ * from `NhAssistantStore.approvalRights`. Chat approvals never offer feedback.
6416
+ */
6417
+ this.canGiveFeedback = input(false, ...(ngDevMode ? [{ debugName: "canGiveFeedback" }] : []));
4827
6418
  this.decide = output();
4828
6419
  this.now = signal(Date.now(), ...(ngDevMode ? [{ debugName: "now" }] : []));
4829
6420
  this.submitted = signal(false, ...(ngDevMode ? [{ debugName: "submitted" }] : []));
6421
+ /** The feedback question is expanded. */
6422
+ this.feedbackOpen = signal(false, ...(ngDevMode ? [{ debugName: "feedbackOpen" }] : []));
6423
+ /** The feedback was stored; the question thanks the person and the action disappears. */
6424
+ this.feedbackSaved = signal(false, ...(ngDevMode ? [{ debugName: "feedbackSaved" }] : []));
6425
+ this.feedbackToggle = viewChild('feedbackToggle', ...(ngDevMode ? [{ debugName: "feedbackToggle" }] : []));
4830
6426
  this.remainingMs = computed(() => Date.parse(this.approval().expiresAt) - this.now(), ...(ngDevMode ? [{ debugName: "remainingMs" }] : []));
4831
6427
  this.expired = computed(() => this.approval().status === 'expired' ||
4832
6428
  (this.approval().status === 'pending' && this.remainingMs() <= 0), ...(ngDevMode ? [{ debugName: "expired" }] : []));
4833
6429
  this.effectiveStatus = computed(() => this.expired() ? 'expired' : this.approval().status, ...(ngDevMode ? [{ debugName: "effectiveStatus" }] : []));
6430
+ /**
6431
+ * Translation key of the status: the kind of a stale proposal, "replaced" for a superseded one,
6432
+ * otherwise the status itself.
6433
+ */
6434
+ this.statusKey = computed(() => {
6435
+ const kind = this.approval().staleKind;
6436
+ if (this.effectiveStatus() === 'stale' && kind) {
6437
+ return `nh-assistant.approval.stale-kind.${kind}`;
6438
+ }
6439
+ return this.effectiveStatus() === 'withdrawn' && this.approval().supersededByApprovalId
6440
+ ? 'nh-assistant.approval.superseded'
6441
+ : `nh-assistant.approval.status.${this.effectiveStatus()}`;
6442
+ }, ...(ngDevMode ? [{ debugName: "statusKey" }] : []));
6443
+ /** Why the agent's rules proposed it; model proposals have none. */
6444
+ this.rationale = computed(() => this.approval().rationale ?? null, ...(ngDevMode ? [{ debugName: "rationale" }] : []));
6445
+ /** The application's explanation of a stale proposal, such as who already did the work. */
6446
+ this.staleExplanation = computed(() => this.effectiveStatus() === 'stale' ? this.approval().staleExplanation ?? null : null, ...(ngDevMode ? [{ debugName: "staleExplanation" }] : []));
4834
6447
  this.actionable = computed(() => this.effectiveStatus() === 'pending', ...(ngDevMode ? [{ debugName: "actionable" }] : []));
6448
+ this.succeeded = computed(() => ['approved', 'executing', 'executed', 'resolved'].includes(this.effectiveStatus()), ...(ngDevMode ? [{ debugName: "succeeded" }] : []));
4835
6449
  this.disabled = computed(() => !this.actionable() || !this.canDecide() || this.deciding() || this.submitted(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
4836
6450
  this.countdown = computed(() => formatNhAssistantCountdown(this.remainingMs()), ...(ngDevMode ? [{ debugName: "countdown" }] : []));
4837
6451
  this.presentedSummary = computed(() => this.approval().presentation?.summary ?? null, ...(ngDevMode ? [{ debugName: "presentedSummary" }] : []));
6452
+ this.feedbackAvailable = computed(() => this.canGiveFeedback() && this.effectiveStatus() === 'rejected', ...(ngDevMode ? [{ debugName: "feedbackAvailable" }] : []));
6453
+ this.feedbackIds = computed(() => [this.approval().approvalId], ...(ngDevMode ? [{ debugName: "feedbackIds" }] : []));
6454
+ this.feedbackPanelId = `nh-assistant-approval-feedback-${nextId$3++}`;
6455
+ /** Translation key of the text shown instead of the buttons when the current user cannot decide. */
6456
+ this.waitingKey = computed(() => {
6457
+ switch (this.waitingFor()) {
6458
+ case 'approver':
6459
+ return 'nh-assistant.approval.waiting-for-approver';
6460
+ case 'other-approver':
6461
+ return 'nh-assistant.approval.waiting-for-other-approver';
6462
+ default:
6463
+ return this.decisionOwner() ? 'nh-assistant.approval.waiting-for' : 'nh-assistant.approval.waiting-for-participant';
6464
+ }
6465
+ }, ...(ngDevMode ? [{ debugName: "waitingKey" }] : []));
4838
6466
  // Tick only while the approval is pending and not yet expired.
4839
6467
  effect(onCleanup => {
4840
6468
  if (this.approval().status !== 'pending') {
@@ -4863,15 +6491,26 @@ class NhAssistantApprovalCardComponent {
4863
6491
  this.submitted.set(true);
4864
6492
  this.decide.emit(decision);
4865
6493
  }
6494
+ toggleFeedback() {
6495
+ this.feedbackOpen.update(open => !open);
6496
+ }
6497
+ onFeedbackSaved() {
6498
+ this.feedbackSaved.set(true);
6499
+ }
6500
+ /** Collapses the question and returns focus to the action that opened it. */
6501
+ onFeedbackDismissed() {
6502
+ this.feedbackOpen.set(false);
6503
+ this.feedbackToggle()?.nativeElement.focus();
6504
+ }
4866
6505
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantApprovalCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4867
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantApprovalCardComponent, isStandalone: true, selector: "nh-assistant-approval-card", inputs: { approval: { classPropertyName: "approval", publicName: "approval", isSignal: true, isRequired: true, transformFunction: null }, deciding: { classPropertyName: "deciding", publicName: "deciding", isSignal: true, isRequired: false, transformFunction: null }, canDecide: { classPropertyName: "canDecide", publicName: "canDecide", isSignal: true, isRequired: false, transformFunction: null }, decisionOwner: { classPropertyName: "decisionOwner", publicName: "decisionOwner", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { decide: "decide" }, host: { properties: { "attr.data-status": "effectiveStatus()" } }, ngImport: i0, template: "<section class=\"card\" [attr.aria-label]=\"'nh-assistant.approval.title' | translate\">\n <header class=\"header\">\n <nh-assistant-icon [name]=\"actionable() ? 'warning' : effectiveStatus() === 'approved' ? 'check' : 'x'\" />\n <h3>{{ 'nh-assistant.approval.title' | translate }}</h3>\n <span class=\"status\">{{ 'nh-assistant.approval.status.' + effectiveStatus() | translate }}</span>\n </header>\n\n @if (approval().presentation; as presentation) {\n <p class=\"tool-name\">{{ presentation.toolDisplayName }}</p>\n <p class=\"summary\">{{ presentation.summary }}</p>\n @if (presentation.fields.length > 0) {\n <dl class=\"presentation-fields\">\n @for (field of presentation.fields; track $index) {\n <div>\n <dt>{{ field.label }}</dt>\n <dd>{{ field.value }}</dd>\n </div>\n }\n </dl>\n }\n @if (presentation.notice) {\n <p class=\"notice\">{{ presentation.notice }}</p>\n }\n } @else {\n <p class=\"summary\">{{ 'nh-assistant.approval.fallback-summary' | translate }}</p>\n }\n\n <details class=\"technical-details\">\n <summary>{{ 'nh-assistant.approval.technical-details' | translate }}</summary>\n <p class=\"label\">{{ 'nh-assistant.approval.tool-id' | translate }}</p>\n <code>{{ approval().toolId }}</code>\n @if (approval().targets.length > 0) {\n <p class=\"label\">{{ 'nh-assistant.approval.targets' | translate }}</p>\n <ul class=\"targets\">\n @for (target of approval().targets; track $index) {\n <li>{{ target }}</li>\n }\n </ul>\n }\n @if (approval().argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.approval.arguments' | translate }}</p>\n <pre>{{ approval().argumentsPreview }}</pre>\n }\n </details>\n\n @if (actionable() && !canDecide()) {\n <p class=\"waiting-for-other\" role=\"status\">\n <nh-assistant-icon name=\"users\" />\n {{ decisionOwner()\n ? ('nh-assistant.approval.waiting-for' | translate: { name: decisionOwner() })\n : ('nh-assistant.approval.waiting-for-participant' | translate) }}\n </p>\n } @else if (actionable()) {\n <p class=\"expiry\" aria-live=\"off\">\n <nh-assistant-icon name=\"clock\" />\n {{ 'nh-assistant.approval.expires-in' | translate: { time: countdown() } }}\n </p>\n <div class=\"actions\">\n <button\n class=\"reject\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.reject-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('reject')\">\n {{ 'nh-assistant.approval.reject' | translate }}\n </button>\n <button\n class=\"approve\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.approve-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('approve')\">\n {{ 'nh-assistant.approval.approve' | translate }}\n </button>\n </div>\n @if (deciding()) {\n <p class=\"deciding\" role=\"status\">{{ 'nh-assistant.approval.deciding' | translate }}</p>\n }\n } @else if (expired()) {\n <p class=\"expiry\">{{ 'nh-assistant.approval.expired' | translate }}</p>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=approved]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=rejected]),:host([data-status=expired]){--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}.card{border:1px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:12px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;color:var(--nha-status-color)}h3{flex:1;margin:0;color:var(--nha-text);font-size:14px;font-weight:700}.status{border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.summary{margin:8px 0 0;font-size:14px;overflow-wrap:anywhere}.tool-name{margin:10px 0 0;font-size:12px;font-weight:700}.presentation-fields{display:grid;gap:6px;margin:10px 0 0}.presentation-fields div{display:grid;grid-template-columns:minmax(90px,.4fr) minmax(0,1fr);gap:8px}.presentation-fields dt{color:var(--nha-text-muted);font-size:12px;font-weight:700}.presentation-fields dd{margin:0;overflow-wrap:anywhere;font-size:13px}.notice{margin:10px 0 0;color:var(--nha-text-muted);font-size:12px}.technical-details{margin-top:10px;color:var(--nha-text-muted);font-size:12px}.technical-details summary{cursor:pointer;font-weight:700}.technical-details code{overflow-wrap:anywhere}.label{margin:10px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.targets{margin:0;padding-left:18px;font-size:13px;overflow-wrap:anywhere}pre{max-height:160px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}.expiry,.deciding,.waiting-for-other{display:flex;align-items:center;gap:6px;margin:10px 0 0;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px}button{min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.55}.reject{color:var(--nha-text);background:transparent}.reject:hover:not(:disabled){background:var(--nha-surface-strong)}.approve{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.approve:hover:not(:disabled){filter:brightness(1.12)}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6506
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantApprovalCardComponent, isStandalone: true, selector: "nh-assistant-approval-card", inputs: { approval: { classPropertyName: "approval", publicName: "approval", isSignal: true, isRequired: true, transformFunction: null }, deciding: { classPropertyName: "deciding", publicName: "deciding", isSignal: true, isRequired: false, transformFunction: null }, canDecide: { classPropertyName: "canDecide", publicName: "canDecide", isSignal: true, isRequired: false, transformFunction: null }, decisionOwner: { classPropertyName: "decisionOwner", publicName: "decisionOwner", isSignal: true, isRequired: false, transformFunction: null }, waitingFor: { classPropertyName: "waitingFor", publicName: "waitingFor", isSignal: true, isRequired: false, transformFunction: null }, canGiveFeedback: { classPropertyName: "canGiveFeedback", publicName: "canGiveFeedback", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { decide: "decide" }, host: { properties: { "attr.data-status": "effectiveStatus()" } }, viewQueries: [{ propertyName: "feedbackToggle", first: true, predicate: ["feedbackToggle"], descendants: true, isSignal: true }], ngImport: i0, template: "<section class=\"card\" [attr.aria-label]=\"'nh-assistant.approval.title' | translate\">\n <header class=\"header\">\n <nh-assistant-icon [name]=\"actionable() ? 'warning' : succeeded() ? 'check' : 'x'\" />\n <h3>{{ 'nh-assistant.approval.title' | translate }}</h3>\n <span class=\"status\">{{ statusKey() | translate }}</span>\n </header>\n\n @if (staleExplanation(); as explanation) {\n <p class=\"stale-explanation\">{{ explanation }}</p>\n }\n\n @if (approval().presentation; as presentation) {\n <p class=\"tool-name\">{{ presentation.toolDisplayName }}</p>\n <p class=\"summary\">{{ presentation.summary }}</p>\n @if (presentation.fields.length > 0) {\n <dl class=\"presentation-fields\">\n @for (field of presentation.fields; track $index) {\n <div>\n <dt>{{ field.label }}</dt>\n <dd>{{ field.value }}</dd>\n </div>\n }\n </dl>\n }\n @if (presentation.notice) {\n <p class=\"notice\">{{ presentation.notice }}</p>\n }\n } @else {\n <p class=\"summary\">{{ 'nh-assistant.approval.fallback-summary' | translate }}</p>\n }\n\n @if (rationale(); as why) {\n <p class=\"rationale\">{{ 'nh-assistant.approval.rationale' | translate: { rationale: why } }}</p>\n }\n\n <details class=\"technical-details\">\n <summary>{{ 'nh-assistant.approval.technical-details' | translate }}</summary>\n <p class=\"label\">{{ 'nh-assistant.approval.tool-id' | translate }}</p>\n <code>{{ approval().toolId }}</code>\n @if (approval().targets.length > 0) {\n <p class=\"label\">{{ 'nh-assistant.approval.targets' | translate }}</p>\n <ul class=\"targets\">\n @for (target of approval().targets; track $index) {\n <li>{{ target }}</li>\n }\n </ul>\n }\n @if (approval().argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.approval.arguments' | translate }}</p>\n <pre>{{ approval().argumentsPreview }}</pre>\n }\n </details>\n\n @if (actionable() && !canDecide()) {\n <p class=\"waiting-for-other\" role=\"status\">\n <nh-assistant-icon name=\"users\" />\n {{ waitingKey() | translate: { name: decisionOwner() } }}\n </p>\n } @else if (actionable()) {\n <p class=\"expiry\" aria-live=\"off\">\n <nh-assistant-icon name=\"clock\" />\n {{ 'nh-assistant.approval.expires-in' | translate: { time: countdown() } }}\n </p>\n <div class=\"actions\">\n <button\n class=\"reject\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.reject-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('reject')\">\n {{ 'nh-assistant.approval.reject' | translate }}\n </button>\n <button\n class=\"approve\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.approve-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('approve')\">\n {{ 'nh-assistant.approval.approve' | translate }}\n </button>\n </div>\n @if (deciding()) {\n <p class=\"deciding\" role=\"status\">{{ 'nh-assistant.approval.deciding' | translate }}</p>\n }\n } @else if (expired()) {\n <p class=\"expiry\">{{ 'nh-assistant.approval.expired' | translate }}</p>\n }\n\n @if (feedbackAvailable()) {\n @if (!feedbackSaved()) {\n <button\n #feedbackToggle\n class=\"feedback-toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"feedbackOpen()\"\n [attr.aria-controls]=\"feedbackOpen() ? feedbackPanelId : null\"\n (click)=\"toggleFeedback()\">\n {{ 'nh-assistant.approval.give-feedback' | translate }}\n </button>\n }\n @if (feedbackOpen()) {\n <nh-assistant-proposal-feedback\n class=\"feedback\"\n [attr.id]=\"feedbackPanelId\"\n [approvalIds]=\"feedbackIds()\"\n (saved)=\"onFeedbackSaved()\"\n (dismissed)=\"onFeedbackDismissed()\" />\n }\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=approved]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=rejected]),:host([data-status=expired]),:host([data-status=stale]){--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}.card{border:1px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:12px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;color:var(--nha-status-color)}h3{flex:1;margin:0;color:var(--nha-text);font-size:14px;font-weight:700}.status{border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.summary{margin:8px 0 0;font-size:14px;overflow-wrap:anywhere}.tool-name{margin:10px 0 0;font-size:12px;font-weight:700}.presentation-fields{display:grid;gap:6px;margin:10px 0 0}.presentation-fields div{display:grid;grid-template-columns:minmax(90px,.4fr) minmax(0,1fr);gap:8px}.presentation-fields dt{color:var(--nha-text-muted);font-size:12px;font-weight:700;overflow-wrap:anywhere}.presentation-fields dd{margin:0;overflow-wrap:anywhere;font-size:13px}.notice{margin:10px 0 0;color:var(--nha-text-muted);font-size:12px}.stale-explanation{margin:8px 0 0;border-radius:8px;padding:8px 10px;color:var(--nha-text);background:var(--nha-surface-strong);font-size:13px;overflow-wrap:anywhere}.rationale{margin:8px 0 0;color:var(--nha-text-muted);font-size:13px;overflow-wrap:anywhere}.technical-details{margin-top:10px;color:var(--nha-text-muted);font-size:12px}.technical-details summary{cursor:pointer;font-weight:700}.technical-details code{overflow-wrap:anywhere}.label{margin:10px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.targets{margin:0;padding-left:18px;font-size:13px;overflow-wrap:anywhere}pre{max-height:160px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}.expiry,.deciding,.waiting-for-other{display:flex;align-items:center;gap:6px;margin:10px 0 0;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px}button{min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.55}.reject{color:var(--nha-text);background:transparent}.reject:hover:not(:disabled){background:var(--nha-surface-strong)}.approve{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.approve:hover:not(:disabled){filter:brightness(1.12)}.feedback-toggle{display:inline-flex;min-height:0;margin-top:10px;border:0;border-radius:6px;padding:2px 0;color:var(--nha-text-muted);background:transparent;font-size:12px;font-weight:650;text-decoration:underline;text-underline-offset:2px}.feedback-toggle:hover:not(:disabled){color:var(--nha-text)}.feedback{display:block;margin-top:10px}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantProposalFeedbackComponent, selector: "nh-assistant-proposal-feedback", inputs: ["approvalIds", "initial"], outputs: ["saved", "dismissed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4868
6507
  }
4869
6508
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantApprovalCardComponent, decorators: [{
4870
6509
  type: Component,
4871
- args: [{ selector: 'nh-assistant-approval-card', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
6510
+ args: [{ selector: 'nh-assistant-approval-card', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent, NhAssistantProposalFeedbackComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
4872
6511
  '[attr.data-status]': 'effectiveStatus()'
4873
- }, template: "<section class=\"card\" [attr.aria-label]=\"'nh-assistant.approval.title' | translate\">\n <header class=\"header\">\n <nh-assistant-icon [name]=\"actionable() ? 'warning' : effectiveStatus() === 'approved' ? 'check' : 'x'\" />\n <h3>{{ 'nh-assistant.approval.title' | translate }}</h3>\n <span class=\"status\">{{ 'nh-assistant.approval.status.' + effectiveStatus() | translate }}</span>\n </header>\n\n @if (approval().presentation; as presentation) {\n <p class=\"tool-name\">{{ presentation.toolDisplayName }}</p>\n <p class=\"summary\">{{ presentation.summary }}</p>\n @if (presentation.fields.length > 0) {\n <dl class=\"presentation-fields\">\n @for (field of presentation.fields; track $index) {\n <div>\n <dt>{{ field.label }}</dt>\n <dd>{{ field.value }}</dd>\n </div>\n }\n </dl>\n }\n @if (presentation.notice) {\n <p class=\"notice\">{{ presentation.notice }}</p>\n }\n } @else {\n <p class=\"summary\">{{ 'nh-assistant.approval.fallback-summary' | translate }}</p>\n }\n\n <details class=\"technical-details\">\n <summary>{{ 'nh-assistant.approval.technical-details' | translate }}</summary>\n <p class=\"label\">{{ 'nh-assistant.approval.tool-id' | translate }}</p>\n <code>{{ approval().toolId }}</code>\n @if (approval().targets.length > 0) {\n <p class=\"label\">{{ 'nh-assistant.approval.targets' | translate }}</p>\n <ul class=\"targets\">\n @for (target of approval().targets; track $index) {\n <li>{{ target }}</li>\n }\n </ul>\n }\n @if (approval().argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.approval.arguments' | translate }}</p>\n <pre>{{ approval().argumentsPreview }}</pre>\n }\n </details>\n\n @if (actionable() && !canDecide()) {\n <p class=\"waiting-for-other\" role=\"status\">\n <nh-assistant-icon name=\"users\" />\n {{ decisionOwner()\n ? ('nh-assistant.approval.waiting-for' | translate: { name: decisionOwner() })\n : ('nh-assistant.approval.waiting-for-participant' | translate) }}\n </p>\n } @else if (actionable()) {\n <p class=\"expiry\" aria-live=\"off\">\n <nh-assistant-icon name=\"clock\" />\n {{ 'nh-assistant.approval.expires-in' | translate: { time: countdown() } }}\n </p>\n <div class=\"actions\">\n <button\n class=\"reject\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.reject-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('reject')\">\n {{ 'nh-assistant.approval.reject' | translate }}\n </button>\n <button\n class=\"approve\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.approve-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('approve')\">\n {{ 'nh-assistant.approval.approve' | translate }}\n </button>\n </div>\n @if (deciding()) {\n <p class=\"deciding\" role=\"status\">{{ 'nh-assistant.approval.deciding' | translate }}</p>\n }\n } @else if (expired()) {\n <p class=\"expiry\">{{ 'nh-assistant.approval.expired' | translate }}</p>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=approved]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=rejected]),:host([data-status=expired]){--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}.card{border:1px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:12px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;color:var(--nha-status-color)}h3{flex:1;margin:0;color:var(--nha-text);font-size:14px;font-weight:700}.status{border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.summary{margin:8px 0 0;font-size:14px;overflow-wrap:anywhere}.tool-name{margin:10px 0 0;font-size:12px;font-weight:700}.presentation-fields{display:grid;gap:6px;margin:10px 0 0}.presentation-fields div{display:grid;grid-template-columns:minmax(90px,.4fr) minmax(0,1fr);gap:8px}.presentation-fields dt{color:var(--nha-text-muted);font-size:12px;font-weight:700}.presentation-fields dd{margin:0;overflow-wrap:anywhere;font-size:13px}.notice{margin:10px 0 0;color:var(--nha-text-muted);font-size:12px}.technical-details{margin-top:10px;color:var(--nha-text-muted);font-size:12px}.technical-details summary{cursor:pointer;font-weight:700}.technical-details code{overflow-wrap:anywhere}.label{margin:10px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.targets{margin:0;padding-left:18px;font-size:13px;overflow-wrap:anywhere}pre{max-height:160px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}.expiry,.deciding,.waiting-for-other{display:flex;align-items:center;gap:6px;margin:10px 0 0;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px}button{min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.55}.reject{color:var(--nha-text);background:transparent}.reject:hover:not(:disabled){background:var(--nha-surface-strong)}.approve{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.approve:hover:not(:disabled){filter:brightness(1.12)}\n"] }]
4874
- }], ctorParameters: () => [], propDecorators: { approval: [{ type: i0.Input, args: [{ isSignal: true, alias: "approval", required: true }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], canDecide: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDecide", required: false }] }], decisionOwner: [{ type: i0.Input, args: [{ isSignal: true, alias: "decisionOwner", required: false }] }], decide: [{ type: i0.Output, args: ["decide"] }] } });
6512
+ }, template: "<section class=\"card\" [attr.aria-label]=\"'nh-assistant.approval.title' | translate\">\n <header class=\"header\">\n <nh-assistant-icon [name]=\"actionable() ? 'warning' : succeeded() ? 'check' : 'x'\" />\n <h3>{{ 'nh-assistant.approval.title' | translate }}</h3>\n <span class=\"status\">{{ statusKey() | translate }}</span>\n </header>\n\n @if (staleExplanation(); as explanation) {\n <p class=\"stale-explanation\">{{ explanation }}</p>\n }\n\n @if (approval().presentation; as presentation) {\n <p class=\"tool-name\">{{ presentation.toolDisplayName }}</p>\n <p class=\"summary\">{{ presentation.summary }}</p>\n @if (presentation.fields.length > 0) {\n <dl class=\"presentation-fields\">\n @for (field of presentation.fields; track $index) {\n <div>\n <dt>{{ field.label }}</dt>\n <dd>{{ field.value }}</dd>\n </div>\n }\n </dl>\n }\n @if (presentation.notice) {\n <p class=\"notice\">{{ presentation.notice }}</p>\n }\n } @else {\n <p class=\"summary\">{{ 'nh-assistant.approval.fallback-summary' | translate }}</p>\n }\n\n @if (rationale(); as why) {\n <p class=\"rationale\">{{ 'nh-assistant.approval.rationale' | translate: { rationale: why } }}</p>\n }\n\n <details class=\"technical-details\">\n <summary>{{ 'nh-assistant.approval.technical-details' | translate }}</summary>\n <p class=\"label\">{{ 'nh-assistant.approval.tool-id' | translate }}</p>\n <code>{{ approval().toolId }}</code>\n @if (approval().targets.length > 0) {\n <p class=\"label\">{{ 'nh-assistant.approval.targets' | translate }}</p>\n <ul class=\"targets\">\n @for (target of approval().targets; track $index) {\n <li>{{ target }}</li>\n }\n </ul>\n }\n @if (approval().argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.approval.arguments' | translate }}</p>\n <pre>{{ approval().argumentsPreview }}</pre>\n }\n </details>\n\n @if (actionable() && !canDecide()) {\n <p class=\"waiting-for-other\" role=\"status\">\n <nh-assistant-icon name=\"users\" />\n {{ waitingKey() | translate: { name: decisionOwner() } }}\n </p>\n } @else if (actionable()) {\n <p class=\"expiry\" aria-live=\"off\">\n <nh-assistant-icon name=\"clock\" />\n {{ 'nh-assistant.approval.expires-in' | translate: { time: countdown() } }}\n </p>\n <div class=\"actions\">\n <button\n class=\"reject\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.reject-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('reject')\">\n {{ 'nh-assistant.approval.reject' | translate }}\n </button>\n <button\n class=\"approve\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.approve-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('approve')\">\n {{ 'nh-assistant.approval.approve' | translate }}\n </button>\n </div>\n @if (deciding()) {\n <p class=\"deciding\" role=\"status\">{{ 'nh-assistant.approval.deciding' | translate }}</p>\n }\n } @else if (expired()) {\n <p class=\"expiry\">{{ 'nh-assistant.approval.expired' | translate }}</p>\n }\n\n @if (feedbackAvailable()) {\n @if (!feedbackSaved()) {\n <button\n #feedbackToggle\n class=\"feedback-toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"feedbackOpen()\"\n [attr.aria-controls]=\"feedbackOpen() ? feedbackPanelId : null\"\n (click)=\"toggleFeedback()\">\n {{ 'nh-assistant.approval.give-feedback' | translate }}\n </button>\n }\n @if (feedbackOpen()) {\n <nh-assistant-proposal-feedback\n class=\"feedback\"\n [attr.id]=\"feedbackPanelId\"\n [approvalIds]=\"feedbackIds()\"\n (saved)=\"onFeedbackSaved()\"\n (dismissed)=\"onFeedbackDismissed()\" />\n }\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=approved]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=rejected]),:host([data-status=expired]),:host([data-status=stale]){--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}.card{border:1px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:12px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;color:var(--nha-status-color)}h3{flex:1;margin:0;color:var(--nha-text);font-size:14px;font-weight:700}.status{border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.summary{margin:8px 0 0;font-size:14px;overflow-wrap:anywhere}.tool-name{margin:10px 0 0;font-size:12px;font-weight:700}.presentation-fields{display:grid;gap:6px;margin:10px 0 0}.presentation-fields div{display:grid;grid-template-columns:minmax(90px,.4fr) minmax(0,1fr);gap:8px}.presentation-fields dt{color:var(--nha-text-muted);font-size:12px;font-weight:700;overflow-wrap:anywhere}.presentation-fields dd{margin:0;overflow-wrap:anywhere;font-size:13px}.notice{margin:10px 0 0;color:var(--nha-text-muted);font-size:12px}.stale-explanation{margin:8px 0 0;border-radius:8px;padding:8px 10px;color:var(--nha-text);background:var(--nha-surface-strong);font-size:13px;overflow-wrap:anywhere}.rationale{margin:8px 0 0;color:var(--nha-text-muted);font-size:13px;overflow-wrap:anywhere}.technical-details{margin-top:10px;color:var(--nha-text-muted);font-size:12px}.technical-details summary{cursor:pointer;font-weight:700}.technical-details code{overflow-wrap:anywhere}.label{margin:10px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.targets{margin:0;padding-left:18px;font-size:13px;overflow-wrap:anywhere}pre{max-height:160px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}.expiry,.deciding,.waiting-for-other{display:flex;align-items:center;gap:6px;margin:10px 0 0;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px}button{min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.55}.reject{color:var(--nha-text);background:transparent}.reject:hover:not(:disabled){background:var(--nha-surface-strong)}.approve{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.approve:hover:not(:disabled){filter:brightness(1.12)}.feedback-toggle{display:inline-flex;min-height:0;margin-top:10px;border:0;border-radius:6px;padding:2px 0;color:var(--nha-text-muted);background:transparent;font-size:12px;font-weight:650;text-decoration:underline;text-underline-offset:2px}.feedback-toggle:hover:not(:disabled){color:var(--nha-text)}.feedback{display:block;margin-top:10px}\n"] }]
6513
+ }], ctorParameters: () => [], propDecorators: { approval: [{ type: i0.Input, args: [{ isSignal: true, alias: "approval", required: true }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], canDecide: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDecide", required: false }] }], decisionOwner: [{ type: i0.Input, args: [{ isSignal: true, alias: "decisionOwner", required: false }] }], waitingFor: [{ type: i0.Input, args: [{ isSignal: true, alias: "waitingFor", required: false }] }], canGiveFeedback: [{ type: i0.Input, args: [{ isSignal: true, alias: "canGiveFeedback", required: false }] }], decide: [{ type: i0.Output, args: ["decide"] }], feedbackToggle: [{ type: i0.ViewChild, args: ['feedbackToggle', { isSignal: true }] }] } });
4875
6514
 
4876
6515
  let nextId$2 = 0;
4877
6516
  const statusIcons = {
@@ -4879,7 +6518,9 @@ const statusIcons = {
4879
6518
  succeeded: 'check',
4880
6519
  failed: 'warning',
4881
6520
  'awaiting-approval': 'clock',
4882
- rejected: 'x'
6521
+ rejected: 'x',
6522
+ // Ended without running: its proposal expired, was withdrawn or became stale.
6523
+ 'not-executed': 'stop'
4883
6524
  };
4884
6525
  /** Shows one tool invocation with its status and expandable input and result previews. */
4885
6526
  class NhAssistantToolCallCardComponent {
@@ -4894,13 +6535,13 @@ class NhAssistantToolCallCardComponent {
4894
6535
  this.expanded.update(expanded => !expanded);
4895
6536
  }
4896
6537
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4897
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantToolCallCardComponent, isStandalone: true, selector: "nh-assistant-tool-call-card", inputs: { part: { classPropertyName: "part", publicName: "part", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-status": "part().status" } }, ngImport: i0, template: "<article class=\"card\" [attr.aria-label]=\"'nh-assistant.tool-call.label' | translate: { name: part().displayName }\">\n <header class=\"header\">\n <span class=\"status-icon\" [class.spinning]=\"part().status === 'running'\">\n <nh-assistant-icon [name]=\"statusIcon()\" />\n </span>\n <span class=\"title\">{{ part().displayName }}</span>\n <span class=\"status\">{{ 'nh-assistant.tool-call.status.' + part().status | translate }}</span>\n @if (hasDetails()) {\n <button\n class=\"toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"detailsId\"\n [attr.aria-label]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n (click)=\"toggle()\">\n <nh-assistant-icon name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n }\n </header>\n\n @if (part().resultCode; as resultCode) {\n <p class=\"result-code\">\n {{ ['nh-assistant.result-codes.' + resultCode, 'nh-assistant.errors.' + resultCode] | nhAssistantTranslate: resultCode }}\n </p>\n }\n\n @if (hasDetails()) {\n <div class=\"details\" [id]=\"detailsId\" [hidden]=\"!expanded()\">\n <p class=\"label\">{{ 'nh-assistant.tool-call.tool-id' | translate }}</p>\n <code>{{ part().toolId }}</code>\n @if (part().argumentsPreview; as argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.arguments' | translate }}</p>\n <pre>{{ argumentsPreview }}</pre>\n }\n @if (part().resultPreview; as resultPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.result' | translate }}</p>\n <pre>{{ resultPreview }}</pre>\n }\n </div>\n }\n</article>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=failed]){--nha-status-color: var(--nha-danger);--nha-status-background: var(--nha-danger-soft)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=rejected]){--nha-status-color: var(--nha-text-muted)}.card{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;min-width:0}.status-icon{display:inline-flex;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.status{flex:none;border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.toggle{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:28px;height:28px}.toggle:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.toggle:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.toggle:disabled{cursor:not-allowed;opacity:.5}.rotated{transform:rotate(180deg)}.result-code{margin:6px 0 0;color:var(--nha-text-muted);font-size:12px}.details{margin-top:8px}.label{margin:8px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}pre{max-height:180px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}nh-assistant-icon{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6538
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantToolCallCardComponent, isStandalone: true, selector: "nh-assistant-tool-call-card", inputs: { part: { classPropertyName: "part", publicName: "part", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-status": "part().status" } }, ngImport: i0, template: "<article class=\"card\" [attr.aria-label]=\"'nh-assistant.tool-call.label' | translate: { name: part().displayName }\">\n <header class=\"header\">\n <span class=\"status-icon\" [class.spinning]=\"part().status === 'running'\">\n <nh-assistant-icon [name]=\"statusIcon()\" />\n </span>\n <span class=\"title\">{{ part().displayName }}</span>\n @if (part().grantSource; as grantSource) {\n <span class=\"grant\" [attr.title]=\"'nh-assistant.tool-call.grant-source.' + grantSource + '-hint' | translate\">\n {{ 'nh-assistant.tool-call.grant-source.' + grantSource | translate }}\n </span>\n }\n <span class=\"status\">{{ 'nh-assistant.tool-call.status.' + part().status | translate }}</span>\n @if (hasDetails()) {\n <button\n class=\"toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"detailsId\"\n [attr.aria-label]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n (click)=\"toggle()\">\n <nh-assistant-icon name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n }\n </header>\n\n @if (part().resultCode; as resultCode) {\n <p class=\"result-code\">\n {{ ['nh-assistant.result-codes.' + resultCode, 'nh-assistant.errors.' + resultCode] | nhAssistantTranslate: resultCode }}\n </p>\n }\n\n @if (hasDetails()) {\n <div class=\"details\" [id]=\"detailsId\" [hidden]=\"!expanded()\">\n <p class=\"label\">{{ 'nh-assistant.tool-call.tool-id' | translate }}</p>\n <code>{{ part().toolId }}</code>\n @if (part().argumentsPreview; as argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.arguments' | translate }}</p>\n <pre>{{ argumentsPreview }}</pre>\n }\n @if (part().resultPreview; as resultPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.result' | translate }}</p>\n <pre>{{ resultPreview }}</pre>\n } @else if (part().restricted) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.result' | translate }}</p>\n <p class=\"restricted\">{{ 'nh-assistant.tool-call.restricted' | translate }}</p>\n }\n </div>\n }\n</article>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=failed]){--nha-status-color: var(--nha-danger);--nha-status-background: var(--nha-danger-soft)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=rejected]){--nha-status-color: var(--nha-text-muted)}:host([data-status=not-executed]){--nha-status-color: var(--nha-text-muted)}:host([data-status=not-executed]) .card{border-left-style:dashed}.card{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;min-width:0}.status-icon{display:inline-flex;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.status{flex:none;border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.toggle{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:28px;height:28px}.toggle:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.toggle:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.toggle:disabled{cursor:not-allowed;opacity:.5}.rotated{transform:rotate(180deg)}.result-code{margin:6px 0 0;color:var(--nha-text-muted);font-size:12px}.details{margin-top:8px}.label{margin:8px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}pre{max-height:180px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}nh-assistant-icon{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}.grant{flex:none;padding:1px 6px;border:1px solid var(--nh-assistant-border, currentColor);border-radius:999px;font-size:.75em;opacity:.85}.restricted{margin:0;font-style:italic;opacity:.8}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4898
6539
  }
4899
6540
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallCardComponent, decorators: [{
4900
6541
  type: Component,
4901
6542
  args: [{ selector: 'nh-assistant-tool-call-card', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
4902
6543
  '[attr.data-status]': 'part().status'
4903
- }, template: "<article class=\"card\" [attr.aria-label]=\"'nh-assistant.tool-call.label' | translate: { name: part().displayName }\">\n <header class=\"header\">\n <span class=\"status-icon\" [class.spinning]=\"part().status === 'running'\">\n <nh-assistant-icon [name]=\"statusIcon()\" />\n </span>\n <span class=\"title\">{{ part().displayName }}</span>\n <span class=\"status\">{{ 'nh-assistant.tool-call.status.' + part().status | translate }}</span>\n @if (hasDetails()) {\n <button\n class=\"toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"detailsId\"\n [attr.aria-label]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n (click)=\"toggle()\">\n <nh-assistant-icon name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n }\n </header>\n\n @if (part().resultCode; as resultCode) {\n <p class=\"result-code\">\n {{ ['nh-assistant.result-codes.' + resultCode, 'nh-assistant.errors.' + resultCode] | nhAssistantTranslate: resultCode }}\n </p>\n }\n\n @if (hasDetails()) {\n <div class=\"details\" [id]=\"detailsId\" [hidden]=\"!expanded()\">\n <p class=\"label\">{{ 'nh-assistant.tool-call.tool-id' | translate }}</p>\n <code>{{ part().toolId }}</code>\n @if (part().argumentsPreview; as argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.arguments' | translate }}</p>\n <pre>{{ argumentsPreview }}</pre>\n }\n @if (part().resultPreview; as resultPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.result' | translate }}</p>\n <pre>{{ resultPreview }}</pre>\n }\n </div>\n }\n</article>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=failed]){--nha-status-color: var(--nha-danger);--nha-status-background: var(--nha-danger-soft)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=rejected]){--nha-status-color: var(--nha-text-muted)}.card{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;min-width:0}.status-icon{display:inline-flex;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.status{flex:none;border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.toggle{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:28px;height:28px}.toggle:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.toggle:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.toggle:disabled{cursor:not-allowed;opacity:.5}.rotated{transform:rotate(180deg)}.result-code{margin:6px 0 0;color:var(--nha-text-muted);font-size:12px}.details{margin-top:8px}.label{margin:8px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}pre{max-height:180px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}nh-assistant-icon{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"] }]
6544
+ }, template: "<article class=\"card\" [attr.aria-label]=\"'nh-assistant.tool-call.label' | translate: { name: part().displayName }\">\n <header class=\"header\">\n <span class=\"status-icon\" [class.spinning]=\"part().status === 'running'\">\n <nh-assistant-icon [name]=\"statusIcon()\" />\n </span>\n <span class=\"title\">{{ part().displayName }}</span>\n @if (part().grantSource; as grantSource) {\n <span class=\"grant\" [attr.title]=\"'nh-assistant.tool-call.grant-source.' + grantSource + '-hint' | translate\">\n {{ 'nh-assistant.tool-call.grant-source.' + grantSource | translate }}\n </span>\n }\n <span class=\"status\">{{ 'nh-assistant.tool-call.status.' + part().status | translate }}</span>\n @if (hasDetails()) {\n <button\n class=\"toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"detailsId\"\n [attr.aria-label]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n (click)=\"toggle()\">\n <nh-assistant-icon name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n }\n </header>\n\n @if (part().resultCode; as resultCode) {\n <p class=\"result-code\">\n {{ ['nh-assistant.result-codes.' + resultCode, 'nh-assistant.errors.' + resultCode] | nhAssistantTranslate: resultCode }}\n </p>\n }\n\n @if (hasDetails()) {\n <div class=\"details\" [id]=\"detailsId\" [hidden]=\"!expanded()\">\n <p class=\"label\">{{ 'nh-assistant.tool-call.tool-id' | translate }}</p>\n <code>{{ part().toolId }}</code>\n @if (part().argumentsPreview; as argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.arguments' | translate }}</p>\n <pre>{{ argumentsPreview }}</pre>\n }\n @if (part().resultPreview; as resultPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.result' | translate }}</p>\n <pre>{{ resultPreview }}</pre>\n } @else if (part().restricted) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.result' | translate }}</p>\n <p class=\"restricted\">{{ 'nh-assistant.tool-call.restricted' | translate }}</p>\n }\n </div>\n }\n</article>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=failed]){--nha-status-color: var(--nha-danger);--nha-status-background: var(--nha-danger-soft)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=rejected]){--nha-status-color: var(--nha-text-muted)}:host([data-status=not-executed]){--nha-status-color: var(--nha-text-muted)}:host([data-status=not-executed]) .card{border-left-style:dashed}.card{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;min-width:0}.status-icon{display:inline-flex;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.status{flex:none;border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.toggle{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:28px;height:28px}.toggle:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.toggle:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.toggle:disabled{cursor:not-allowed;opacity:.5}.rotated{transform:rotate(180deg)}.result-code{margin:6px 0 0;color:var(--nha-text-muted);font-size:12px}.details{margin-top:8px}.label{margin:8px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}pre{max-height:180px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}nh-assistant-icon{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}.grant{flex:none;padding:1px 6px;border:1px solid var(--nh-assistant-border, currentColor);border-radius:999px;font-size:.75em;opacity:.85}.restricted{margin:0;font-style:italic;opacity:.8}\n"] }]
4904
6545
  }], propDecorators: { part: [{ type: i0.Input, args: [{ isSignal: true, alias: "part", required: true }] }] } });
4905
6546
 
4906
6547
  let nextId$1 = 0;
@@ -4908,6 +6549,7 @@ const stateIcons = {
4908
6549
  'awaiting-approval': 'clock',
4909
6550
  running: 'tool',
4910
6551
  failed: 'warning',
6552
+ 'not-executed': 'stop',
4911
6553
  succeeded: 'check'
4912
6554
  };
4913
6555
  /**
@@ -4930,12 +6572,17 @@ class NhAssistantToolCallGroupComponent {
4930
6572
  this.total = computed(() => this.calls().length, ...(ngDevMode ? [{ debugName: "total" }] : []));
4931
6573
  this.finished = computed(() => this.calls().filter(call => !isActive(call)).length, ...(ngDevMode ? [{ debugName: "finished" }] : []));
4932
6574
  this.failed = computed(() => this.calls().filter(call => call.status === 'failed').length, ...(ngDevMode ? [{ debugName: "failed" }] : []));
6575
+ /** Calls that finished without running; they never wait. */
6576
+ this.notExecuted = computed(() => this.calls().filter(call => call.status === 'not-executed').length, ...(ngDevMode ? [{ debugName: "notExecuted" }] : []));
4933
6577
  this.state = computed(() => {
4934
6578
  const current = this.current();
4935
6579
  if (current) {
4936
6580
  return current.status === 'awaiting-approval' ? 'awaiting-approval' : 'running';
4937
6581
  }
4938
- return this.failed() > 0 ? 'failed' : 'succeeded';
6582
+ if (this.failed() > 0) {
6583
+ return 'failed';
6584
+ }
6585
+ return this.notExecuted() > 0 ? 'not-executed' : 'succeeded';
4939
6586
  }, ...(ngDevMode ? [{ debugName: "state" }] : []));
4940
6587
  this.stateIcon = computed(() => stateIcons[this.state()], ...(ngDevMode ? [{ debugName: "stateIcon" }] : []));
4941
6588
  }
@@ -4943,13 +6590,13 @@ class NhAssistantToolCallGroupComponent {
4943
6590
  this.expanded.update(expanded => !expanded);
4944
6591
  }
4945
6592
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4946
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantToolCallGroupComponent, isStandalone: true, selector: "nh-assistant-tool-call-group", inputs: { calls: { classPropertyName: "calls", publicName: "calls", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-status": "state()" } }, ngImport: i0, template: "<section class=\"group\">\n <button\n class=\"summary\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"callsId\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-group.hide-calls' : 'nh-assistant.tool-group.show-calls') | translate\"\n (click)=\"toggle()\">\n <span class=\"status-icon\" [class.spinning]=\"state() === 'running'\">\n <nh-assistant-icon [name]=\"stateIcon()\" />\n </span>\n <span class=\"title\">\n @switch (state()) {\n @case ('running') {\n {{ 'nh-assistant.tool-group.running' | translate: { name: current()?.displayName } }}\n }\n @case ('awaiting-approval') {\n {{ 'nh-assistant.tool-group.awaiting-approval' | translate: { name: current()?.displayName } }}\n }\n @default {\n {{ (total() === 1 ? 'nh-assistant.tool-group.used-one' : 'nh-assistant.tool-group.used-many') | translate: { count: total() } }}\n }\n }\n </span>\n @if (state() === 'running' || state() === 'awaiting-approval') {\n <span class=\"count\">{{ 'nh-assistant.tool-group.progress' | translate: { finished: finished(), total: total() } }}</span>\n } @else if (failed() > 0) {\n <span class=\"count failed\">{{ 'nh-assistant.tool-group.failed' | translate: { count: failed() } }}</span>\n }\n <nh-assistant-icon class=\"chevron\" name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n\n <div class=\"calls\" [id]=\"callsId\" [hidden]=\"!expanded()\">\n @if (expanded()) {\n @for (call of calls(); track call.invocationId) {\n <nh-assistant-tool-call-card [part]=\"call\" />\n }\n }\n </div>\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success)}:host([data-status=failed]){--nha-status-color: var(--nha-danger)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning)}.group{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);color:var(--nha-text);background:var(--nha-background)}.summary{display:flex;align-items:center;gap:8px;width:100%;min-width:0;min-height:40px;border:0;border-radius:var(--nha-radius-control);padding:8px 10px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.summary:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.summary:hover{background:var(--nha-surface)}.status-icon{display:inline-flex;flex:none;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.count{flex:none;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.count.failed{border-radius:999px;padding:2px 8px;color:var(--nha-danger);background:var(--nha-danger-soft);font-size:11px;font-weight:700}.chevron{flex:none;color:var(--nha-text-muted)}.rotated{transform:rotate(180deg)}.calls{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--nha-border);padding:8px}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}.chevron{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantToolCallCardComponent, selector: "nh-assistant-tool-call-card", inputs: ["part"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6593
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantToolCallGroupComponent, isStandalone: true, selector: "nh-assistant-tool-call-group", inputs: { calls: { classPropertyName: "calls", publicName: "calls", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-status": "state()" } }, ngImport: i0, template: "<section class=\"group\">\n <button\n class=\"summary\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"callsId\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-group.hide-calls' : 'nh-assistant.tool-group.show-calls') | translate\"\n (click)=\"toggle()\">\n <span class=\"status-icon\" [class.spinning]=\"state() === 'running'\">\n <nh-assistant-icon [name]=\"stateIcon()\" />\n </span>\n <span class=\"title\">\n @switch (state()) {\n @case ('running') {\n {{ 'nh-assistant.tool-group.running' | translate: { name: current()?.displayName } }}\n }\n @case ('awaiting-approval') {\n {{ 'nh-assistant.tool-group.awaiting-approval' | translate: { name: current()?.displayName } }}\n }\n @default {\n {{ (total() === 1 ? 'nh-assistant.tool-group.used-one' : 'nh-assistant.tool-group.used-many') | translate: { count: total() } }}\n }\n }\n </span>\n @if (state() === 'running' || state() === 'awaiting-approval') {\n <span class=\"count\">{{ 'nh-assistant.tool-group.progress' | translate: { finished: finished(), total: total() } }}</span>\n } @else {\n @if (failed() > 0) {\n <span class=\"count failed\">{{ 'nh-assistant.tool-group.failed' | translate: { count: failed() } }}</span>\n }\n @if (notExecuted() > 0) {\n <span class=\"count not-executed\">{{ 'nh-assistant.tool-group.not-executed' | translate: { count: notExecuted() } }}</span>\n }\n }\n <nh-assistant-icon class=\"chevron\" name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n\n <div class=\"calls\" [id]=\"callsId\" [hidden]=\"!expanded()\">\n @if (expanded()) {\n @for (call of calls(); track call.invocationId) {\n <nh-assistant-tool-call-card [part]=\"call\" />\n }\n }\n </div>\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success)}:host([data-status=failed]){--nha-status-color: var(--nha-danger)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning)}:host([data-status=not-executed]){--nha-status-color: var(--nha-text-muted)}:host([data-status=not-executed]) .group{border-left-style:dashed}.group{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);color:var(--nha-text);background:var(--nha-background)}.summary{display:flex;align-items:center;gap:8px;width:100%;min-width:0;min-height:40px;border:0;border-radius:var(--nha-radius-control);padding:8px 10px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.summary:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.summary:hover{background:var(--nha-surface)}.status-icon{display:inline-flex;flex:none;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.count{flex:none;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.count.failed,.count.not-executed{border-radius:999px;padding:2px 8px;font-size:11px;font-weight:700}.count.failed{color:var(--nha-danger);background:var(--nha-danger-soft)}.count.not-executed{color:var(--nha-text-muted);background:var(--nha-surface-strong)}.chevron{flex:none;color:var(--nha-text-muted)}.rotated{transform:rotate(180deg)}.calls{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--nha-border);padding:8px}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}.chevron{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantToolCallCardComponent, selector: "nh-assistant-tool-call-card", inputs: ["part"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4947
6594
  }
4948
6595
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallGroupComponent, decorators: [{
4949
6596
  type: Component,
4950
6597
  args: [{ selector: 'nh-assistant-tool-call-group', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent, NhAssistantToolCallCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
4951
6598
  '[attr.data-status]': 'state()'
4952
- }, template: "<section class=\"group\">\n <button\n class=\"summary\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"callsId\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-group.hide-calls' : 'nh-assistant.tool-group.show-calls') | translate\"\n (click)=\"toggle()\">\n <span class=\"status-icon\" [class.spinning]=\"state() === 'running'\">\n <nh-assistant-icon [name]=\"stateIcon()\" />\n </span>\n <span class=\"title\">\n @switch (state()) {\n @case ('running') {\n {{ 'nh-assistant.tool-group.running' | translate: { name: current()?.displayName } }}\n }\n @case ('awaiting-approval') {\n {{ 'nh-assistant.tool-group.awaiting-approval' | translate: { name: current()?.displayName } }}\n }\n @default {\n {{ (total() === 1 ? 'nh-assistant.tool-group.used-one' : 'nh-assistant.tool-group.used-many') | translate: { count: total() } }}\n }\n }\n </span>\n @if (state() === 'running' || state() === 'awaiting-approval') {\n <span class=\"count\">{{ 'nh-assistant.tool-group.progress' | translate: { finished: finished(), total: total() } }}</span>\n } @else if (failed() > 0) {\n <span class=\"count failed\">{{ 'nh-assistant.tool-group.failed' | translate: { count: failed() } }}</span>\n }\n <nh-assistant-icon class=\"chevron\" name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n\n <div class=\"calls\" [id]=\"callsId\" [hidden]=\"!expanded()\">\n @if (expanded()) {\n @for (call of calls(); track call.invocationId) {\n <nh-assistant-tool-call-card [part]=\"call\" />\n }\n }\n </div>\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success)}:host([data-status=failed]){--nha-status-color: var(--nha-danger)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning)}.group{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);color:var(--nha-text);background:var(--nha-background)}.summary{display:flex;align-items:center;gap:8px;width:100%;min-width:0;min-height:40px;border:0;border-radius:var(--nha-radius-control);padding:8px 10px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.summary:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.summary:hover{background:var(--nha-surface)}.status-icon{display:inline-flex;flex:none;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.count{flex:none;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.count.failed{border-radius:999px;padding:2px 8px;color:var(--nha-danger);background:var(--nha-danger-soft);font-size:11px;font-weight:700}.chevron{flex:none;color:var(--nha-text-muted)}.rotated{transform:rotate(180deg)}.calls{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--nha-border);padding:8px}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}.chevron{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"] }]
6599
+ }, template: "<section class=\"group\">\n <button\n class=\"summary\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"callsId\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-group.hide-calls' : 'nh-assistant.tool-group.show-calls') | translate\"\n (click)=\"toggle()\">\n <span class=\"status-icon\" [class.spinning]=\"state() === 'running'\">\n <nh-assistant-icon [name]=\"stateIcon()\" />\n </span>\n <span class=\"title\">\n @switch (state()) {\n @case ('running') {\n {{ 'nh-assistant.tool-group.running' | translate: { name: current()?.displayName } }}\n }\n @case ('awaiting-approval') {\n {{ 'nh-assistant.tool-group.awaiting-approval' | translate: { name: current()?.displayName } }}\n }\n @default {\n {{ (total() === 1 ? 'nh-assistant.tool-group.used-one' : 'nh-assistant.tool-group.used-many') | translate: { count: total() } }}\n }\n }\n </span>\n @if (state() === 'running' || state() === 'awaiting-approval') {\n <span class=\"count\">{{ 'nh-assistant.tool-group.progress' | translate: { finished: finished(), total: total() } }}</span>\n } @else {\n @if (failed() > 0) {\n <span class=\"count failed\">{{ 'nh-assistant.tool-group.failed' | translate: { count: failed() } }}</span>\n }\n @if (notExecuted() > 0) {\n <span class=\"count not-executed\">{{ 'nh-assistant.tool-group.not-executed' | translate: { count: notExecuted() } }}</span>\n }\n }\n <nh-assistant-icon class=\"chevron\" name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n\n <div class=\"calls\" [id]=\"callsId\" [hidden]=\"!expanded()\">\n @if (expanded()) {\n @for (call of calls(); track call.invocationId) {\n <nh-assistant-tool-call-card [part]=\"call\" />\n }\n }\n </div>\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success)}:host([data-status=failed]){--nha-status-color: var(--nha-danger)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning)}:host([data-status=not-executed]){--nha-status-color: var(--nha-text-muted)}:host([data-status=not-executed]) .group{border-left-style:dashed}.group{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);color:var(--nha-text);background:var(--nha-background)}.summary{display:flex;align-items:center;gap:8px;width:100%;min-width:0;min-height:40px;border:0;border-radius:var(--nha-radius-control);padding:8px 10px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.summary:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.summary:hover{background:var(--nha-surface)}.status-icon{display:inline-flex;flex:none;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.count{flex:none;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.count.failed,.count.not-executed{border-radius:999px;padding:2px 8px;font-size:11px;font-weight:700}.count.failed{color:var(--nha-danger);background:var(--nha-danger-soft)}.count.not-executed{color:var(--nha-text-muted);background:var(--nha-surface-strong)}.chevron{flex:none;color:var(--nha-text-muted)}.rotated{transform:rotate(180deg)}.calls{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--nha-border);padding:8px}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}.chevron{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"] }]
4953
6600
  }], propDecorators: { calls: [{ type: i0.Input, args: [{ isSignal: true, alias: "calls", required: true }] }] } });
4954
6601
  function isActive(call) {
4955
6602
  return call.status === 'running' || call.status === 'awaiting-approval';
@@ -4970,8 +6617,9 @@ const stickToBottomDistance = 120;
4970
6617
  /**
4971
6618
  * The messages of a conversation as a live log. Assistant text renders as sanitized
4972
6619
  * Markdown, consecutive tool calls as one collapsible group that counts along, and
4973
- * approvals as cards. While a turn runs without visible progress, a working indicator
4974
- * shows the elapsed time. The thread follows new content while the user is at the bottom.
6620
+ * approvals as cards. Messages a trigger wrote into an agent thread read as events, not as
6621
+ * a person. While a turn runs without visible progress, a working indicator shows the
6622
+ * elapsed time. The thread follows new content while the user is at the bottom.
4975
6623
  */
4976
6624
  class NhAssistantThreadComponent {
4977
6625
  constructor() {
@@ -4988,7 +6636,17 @@ class NhAssistantThreadComponent {
4988
6636
  this.canDecide = input(true, ...(ngDevMode ? [{ debugName: "canDecide" }] : []));
4989
6637
  /** Name of the participant who decides the pending approval, when it is someone else. */
4990
6638
  this.decisionOwner = input(null, ...(ngDevMode ? [{ debugName: "decisionOwner" }] : []));
6639
+ /**
6640
+ * Who may decide each pending approval, by approval id, for example `NhAssistantStore.approvalRights`.
6641
+ * An agent thread can hold several open proposals that the subject's approvers decide. Approvals
6642
+ * without an entry use `canDecide` and `decisionOwner`. An entry with `canGiveFeedback` lets the
6643
+ * card of a rejected proposal ask what was wrong.
6644
+ */
6645
+ this.approvalRights = input(null, ...(ngDevMode ? [{ debugName: "approvalRights" }] : []));
6646
+ /** The decision about an approval card, without the approval it belongs to. */
4991
6647
  this.approvalDecision = output();
6648
+ /** The decision about an approval card together with its approval id. */
6649
+ this.approvalChoice = output();
4992
6650
  this.names = computed(() => new Map(this.members().map(member => [member.actorId, member.displayName])), ...(ngDevMode ? [{ debugName: "names" }] : []));
4993
6651
  this.virtualThreshold = NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD;
4994
6652
  this.virtual = computed(() => this.messages().length > NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, ...(ngDevMode ? [{ debugName: "virtual" }] : []));
@@ -5047,13 +6705,34 @@ class NhAssistantThreadComponent {
5047
6705
  trackMessage(_index, message) {
5048
6706
  return message.id;
5049
6707
  }
6708
+ /** True for a user message a trigger wrote into an agent thread. */
6709
+ isTrigger(message) {
6710
+ return message.role === 'user' && message.authorKind === 'trigger';
6711
+ }
6712
+ /** True for an assistant message the agent's rules wrote without a model. */
6713
+ isRule(message) {
6714
+ return message.role === 'assistant' && message.authorKind === 'rule';
6715
+ }
5050
6716
  /** True for a user message written by another person in a shared conversation. */
5051
6717
  isOthers(message) {
5052
6718
  return message.role === 'user'
6719
+ && !this.isTrigger(message)
5053
6720
  && !!message.authorActorId
5054
6721
  && !!this.currentActorId()
5055
6722
  && message.authorActorId !== this.currentActorId();
5056
6723
  }
6724
+ /** Whether and why the current user may decide an approval card. */
6725
+ rightFor(approval) {
6726
+ return this.approvalRights()?.get(approval.approvalId) ?? {
6727
+ canDecide: this.canDecide(),
6728
+ waitingFor: 'turn',
6729
+ decisionOwner: this.decisionOwner()
6730
+ };
6731
+ }
6732
+ choose(approval, decision) {
6733
+ this.approvalDecision.emit(decision);
6734
+ this.approvalChoice.emit({ approvalId: approval.approvalId, decision });
6735
+ }
5057
6736
  /** Display name of another person's message, or `null` when the application provides none. */
5058
6737
  authorName(message) {
5059
6738
  return message.authorActorId ? this.names().get(message.authorActorId) ?? null : null;
@@ -5074,7 +6753,7 @@ class NhAssistantThreadComponent {
5074
6753
  }
5075
6754
  }
5076
6755
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantThreadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5077
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantThreadComponent, isStandalone: true, selector: "nh-assistant-thread", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, deciding: { classPropertyName: "deciding", publicName: "deciding", isSignal: true, isRequired: false, transformFunction: null }, markdown: { classPropertyName: "markdown", publicName: "markdown", isSignal: true, isRequired: false, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: false, transformFunction: null }, currentActorId: { classPropertyName: "currentActorId", publicName: "currentActorId", isSignal: true, isRequired: false, transformFunction: null }, canDecide: { classPropertyName: "canDecide", publicName: "canDecide", isSignal: true, isRequired: false, transformFunction: null }, decisionOwner: { classPropertyName: "decisionOwner", publicName: "decisionOwner", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { approvalDecision: "approvalDecision" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article class=\"message\" [attr.data-role]=\"message.role\" [attr.data-others]=\"isOthers(message) ? 'true' : null\">\n @if (isOthers(message)) {\n <p class=\"author\">{{ authorName(message) ?? ('nh-assistant.thread.participant' | translate) }}</p>\n } @else {\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n }\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"canDecide()\"\n [decisionOwner]=\"decisionOwner()\"\n (decide)=\"approvalDecision.emit($event)\" />\n }\n }\n }\n </article>\n</ng-template>\n\n<ng-template #workingTemplate>\n <p class=\"working\" role=\"status\">\n <span class=\"dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span>\n {{ 'nh-assistant.thread.working' | translate }}\n @if (elapsedSeconds() > 0) {\n <span class=\"elapsed\">{{ 'nh-assistant.thread.elapsed' | translate: { seconds: elapsedSeconds() } }}</span>\n }\n </p>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.log{display:flex;flex:1;flex-direction:column;min-height:0}.scroller{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px;outline:none}.scroller:focus-visible{box-shadow:inset 0 0 0 2px var(--nha-focus)}cdk-virtual-scroll-viewport.scroller{height:100%}.row+.row{margin-top:20px}.message{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0}.message[data-role=user]{align-items:flex-end}.author{margin:0;color:var(--nha-text-muted);font-size:11px;font-weight:600}.bubble{max-width:100%;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}.plain{white-space:pre-wrap}.message[data-role=user] .bubble{max-width:88%;color:var(--nha-user-text);background:var(--nha-user-bubble)}.message[data-others=true]{align-items:flex-start}.message[data-others=true] .bubble{color:var(--nha-text);background:var(--nha-surface-strong)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.part{width:100%}:host ::ng-deep .markdown :first-child{margin-top:0}:host ::ng-deep .markdown :last-child{margin-bottom:0}:host ::ng-deep .markdown p,:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol,:host ::ng-deep .markdown pre,:host ::ng-deep .markdown blockquote,:host ::ng-deep .markdown table{margin:0 0 8px}:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol{padding-left:20px}:host ::ng-deep .markdown h1,:host ::ng-deep .markdown h2,:host ::ng-deep .markdown h3,:host ::ng-deep .markdown h4,:host ::ng-deep .markdown h5,:host ::ng-deep .markdown h6{margin:10px 0 6px;font-size:15px;line-height:1.3}:host ::ng-deep .markdown a{color:var(--nha-highlight);text-decoration:underline}:host ::ng-deep .markdown a:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host ::ng-deep .markdown code{border-radius:6px;padding:1px 4px;background:var(--nha-surface-strong);font-family:var(--nha-mono);font-size:12.5px}:host ::ng-deep .markdown pre{overflow-x:auto;border-radius:8px;padding:8px;background:var(--nha-surface-strong)}:host ::ng-deep .markdown pre code{padding:0;background:none}:host ::ng-deep .markdown blockquote{border-left:3px solid var(--nha-border);padding-left:10px;color:var(--nha-text-muted)}:host ::ng-deep .markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;font-size:13px}:host ::ng-deep .markdown th,:host ::ng-deep .markdown td{border:1px solid var(--nha-border);padding:4px 8px;text-align:left}.working{display:flex;align-items:center;gap:8px;margin:14px 0 0;color:var(--nha-text-muted);font-size:13px}.elapsed{font-variant-numeric:tabular-nums}.dots{display:inline-flex;gap:3px}.dots span{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.6}@media(prefers-reduced-motion:no-preference){.dots span{animation:nha-bounce 1s ease-in-out infinite}.dots span:nth-child(2){animation-delay:.15s}.dots span:nth-child(3){animation-delay:.3s}}@keyframes nha-bounce{0%,80%,to{transform:translateY(0);opacity:.35}40%{transform:translateY(-3px);opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: NhAssistantToolCallGroupComponent, selector: "nh-assistant-tool-call-group", inputs: ["calls"] }, { kind: "component", type: NhAssistantApprovalCardComponent, selector: "nh-assistant-approval-card", inputs: ["approval", "deciding", "canDecide", "decisionOwner"], outputs: ["decide"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantMarkdownPipe, name: "nhAssistantMarkdown" }, { kind: "pipe", type: NhAssistantPartGroupsPipe, name: "nhAssistantPartGroups" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6756
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantThreadComponent, isStandalone: true, selector: "nh-assistant-thread", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, deciding: { classPropertyName: "deciding", publicName: "deciding", isSignal: true, isRequired: false, transformFunction: null }, markdown: { classPropertyName: "markdown", publicName: "markdown", isSignal: true, isRequired: false, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: false, transformFunction: null }, currentActorId: { classPropertyName: "currentActorId", publicName: "currentActorId", isSignal: true, isRequired: false, transformFunction: null }, canDecide: { classPropertyName: "canDecide", publicName: "canDecide", isSignal: true, isRequired: false, transformFunction: null }, decisionOwner: { classPropertyName: "decisionOwner", publicName: "decisionOwner", isSignal: true, isRequired: false, transformFunction: null }, approvalRights: { classPropertyName: "approvalRights", publicName: "approvalRights", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { approvalDecision: "approvalDecision", approvalChoice: "approvalChoice" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article\n class=\"message\"\n [attr.data-role]=\"message.role\"\n [attr.data-others]=\"isOthers(message) ? 'true' : null\"\n [attr.data-author]=\"isTrigger(message) ? 'trigger' : isRule(message) ? 'rule' : null\">\n @if (isTrigger(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.trigger' | translate }}</p>\n } @else if (isRule(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.rule' | translate }}</p>\n } @else if (isOthers(message)) {\n <p class=\"author\">{{ authorName(message) ?? ('nh-assistant.thread.participant' | translate) }}</p>\n } @else {\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n }\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"rightFor(group.part).canDecide\"\n [decisionOwner]=\"rightFor(group.part).decisionOwner\"\n [waitingFor]=\"rightFor(group.part).waitingFor\"\n [canGiveFeedback]=\"rightFor(group.part).canGiveFeedback === true\"\n (decide)=\"choose(group.part, $event)\" />\n }\n }\n }\n </article>\n</ng-template>\n\n<ng-template #workingTemplate>\n <p class=\"working\" role=\"status\">\n <span class=\"dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span>\n {{ 'nh-assistant.thread.working' | translate }}\n @if (elapsedSeconds() > 0) {\n <span class=\"elapsed\">{{ 'nh-assistant.thread.elapsed' | translate: { seconds: elapsedSeconds() } }}</span>\n }\n </p>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.log{display:flex;flex:1;flex-direction:column;min-height:0}.scroller{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px;outline:none}.scroller:focus-visible{box-shadow:inset 0 0 0 2px var(--nha-focus)}cdk-virtual-scroll-viewport.scroller{height:100%}.row+.row{margin-top:20px}.message{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0}.message[data-role=user]{align-items:flex-end}.author{margin:0;color:var(--nha-text-muted);font-size:11px;font-weight:600}.bubble{max-width:100%;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}.plain{white-space:pre-wrap}.message[data-role=user] .bubble{max-width:88%;color:var(--nha-user-text);background:var(--nha-user-bubble)}.message[data-others=true]{align-items:flex-start}.message[data-others=true] .bubble{color:var(--nha-text);background:var(--nha-surface-strong)}.message[data-author=trigger]{align-items:stretch}.message[data-author=trigger] .bubble{max-width:none;color:var(--nha-text-muted);background:transparent;border:1px dashed var(--nha-border)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.part{width:100%}:host ::ng-deep .markdown :first-child{margin-top:0}:host ::ng-deep .markdown :last-child{margin-bottom:0}:host ::ng-deep .markdown p,:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol,:host ::ng-deep .markdown pre,:host ::ng-deep .markdown blockquote,:host ::ng-deep .markdown table{margin:0 0 8px}:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol{padding-left:20px}:host ::ng-deep .markdown h1,:host ::ng-deep .markdown h2,:host ::ng-deep .markdown h3,:host ::ng-deep .markdown h4,:host ::ng-deep .markdown h5,:host ::ng-deep .markdown h6{margin:10px 0 6px;font-size:15px;line-height:1.3}:host ::ng-deep .markdown a{color:var(--nha-highlight);text-decoration:underline}:host ::ng-deep .markdown a:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host ::ng-deep .markdown code{border-radius:6px;padding:1px 4px;background:var(--nha-surface-strong);font-family:var(--nha-mono);font-size:12.5px}:host ::ng-deep .markdown pre{overflow-x:auto;border-radius:8px;padding:8px;background:var(--nha-surface-strong)}:host ::ng-deep .markdown pre code{padding:0;background:none}:host ::ng-deep .markdown blockquote{border-left:3px solid var(--nha-border);padding-left:10px;color:var(--nha-text-muted)}:host ::ng-deep .markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;font-size:13px}:host ::ng-deep .markdown th,:host ::ng-deep .markdown td{border:1px solid var(--nha-border);padding:4px 8px;text-align:left}.working{display:flex;align-items:center;gap:8px;margin:14px 0 0;color:var(--nha-text-muted);font-size:13px}.elapsed{font-variant-numeric:tabular-nums}.dots{display:inline-flex;gap:3px}.dots span{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.6}@media(prefers-reduced-motion:no-preference){.dots span{animation:nha-bounce 1s ease-in-out infinite}.dots span:nth-child(2){animation-delay:.15s}.dots span:nth-child(3){animation-delay:.3s}}@keyframes nha-bounce{0%,80%,to{transform:translateY(0);opacity:.35}40%{transform:translateY(-3px);opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: NhAssistantToolCallGroupComponent, selector: "nh-assistant-tool-call-group", inputs: ["calls"] }, { kind: "component", type: NhAssistantApprovalCardComponent, selector: "nh-assistant-approval-card", inputs: ["approval", "deciding", "canDecide", "decisionOwner", "waitingFor", "canGiveFeedback"], outputs: ["decide"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantMarkdownPipe, name: "nhAssistantMarkdown" }, { kind: "pipe", type: NhAssistantPartGroupsPipe, name: "nhAssistantPartGroups" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5078
6757
  }
5079
6758
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantThreadComponent, decorators: [{
5080
6759
  type: Component,
@@ -5086,8 +6765,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
5086
6765
  NhAssistantPartGroupsPipe,
5087
6766
  NhAssistantToolCallGroupComponent,
5088
6767
  NhAssistantApprovalCardComponent
5089
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article class=\"message\" [attr.data-role]=\"message.role\" [attr.data-others]=\"isOthers(message) ? 'true' : null\">\n @if (isOthers(message)) {\n <p class=\"author\">{{ authorName(message) ?? ('nh-assistant.thread.participant' | translate) }}</p>\n } @else {\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n }\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"canDecide()\"\n [decisionOwner]=\"decisionOwner()\"\n (decide)=\"approvalDecision.emit($event)\" />\n }\n }\n }\n </article>\n</ng-template>\n\n<ng-template #workingTemplate>\n <p class=\"working\" role=\"status\">\n <span class=\"dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span>\n {{ 'nh-assistant.thread.working' | translate }}\n @if (elapsedSeconds() > 0) {\n <span class=\"elapsed\">{{ 'nh-assistant.thread.elapsed' | translate: { seconds: elapsedSeconds() } }}</span>\n }\n </p>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.log{display:flex;flex:1;flex-direction:column;min-height:0}.scroller{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px;outline:none}.scroller:focus-visible{box-shadow:inset 0 0 0 2px var(--nha-focus)}cdk-virtual-scroll-viewport.scroller{height:100%}.row+.row{margin-top:20px}.message{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0}.message[data-role=user]{align-items:flex-end}.author{margin:0;color:var(--nha-text-muted);font-size:11px;font-weight:600}.bubble{max-width:100%;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}.plain{white-space:pre-wrap}.message[data-role=user] .bubble{max-width:88%;color:var(--nha-user-text);background:var(--nha-user-bubble)}.message[data-others=true]{align-items:flex-start}.message[data-others=true] .bubble{color:var(--nha-text);background:var(--nha-surface-strong)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.part{width:100%}:host ::ng-deep .markdown :first-child{margin-top:0}:host ::ng-deep .markdown :last-child{margin-bottom:0}:host ::ng-deep .markdown p,:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol,:host ::ng-deep .markdown pre,:host ::ng-deep .markdown blockquote,:host ::ng-deep .markdown table{margin:0 0 8px}:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol{padding-left:20px}:host ::ng-deep .markdown h1,:host ::ng-deep .markdown h2,:host ::ng-deep .markdown h3,:host ::ng-deep .markdown h4,:host ::ng-deep .markdown h5,:host ::ng-deep .markdown h6{margin:10px 0 6px;font-size:15px;line-height:1.3}:host ::ng-deep .markdown a{color:var(--nha-highlight);text-decoration:underline}:host ::ng-deep .markdown a:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host ::ng-deep .markdown code{border-radius:6px;padding:1px 4px;background:var(--nha-surface-strong);font-family:var(--nha-mono);font-size:12.5px}:host ::ng-deep .markdown pre{overflow-x:auto;border-radius:8px;padding:8px;background:var(--nha-surface-strong)}:host ::ng-deep .markdown pre code{padding:0;background:none}:host ::ng-deep .markdown blockquote{border-left:3px solid var(--nha-border);padding-left:10px;color:var(--nha-text-muted)}:host ::ng-deep .markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;font-size:13px}:host ::ng-deep .markdown th,:host ::ng-deep .markdown td{border:1px solid var(--nha-border);padding:4px 8px;text-align:left}.working{display:flex;align-items:center;gap:8px;margin:14px 0 0;color:var(--nha-text-muted);font-size:13px}.elapsed{font-variant-numeric:tabular-nums}.dots{display:inline-flex;gap:3px}.dots span{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.6}@media(prefers-reduced-motion:no-preference){.dots span{animation:nha-bounce 1s ease-in-out infinite}.dots span:nth-child(2){animation-delay:.15s}.dots span:nth-child(3){animation-delay:.3s}}@keyframes nha-bounce{0%,80%,to{transform:translateY(0);opacity:.35}40%{transform:translateY(-3px);opacity:1}}\n"] }]
5090
- }], ctorParameters: () => [], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: true }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], markdown: [{ type: i0.Input, args: [{ isSignal: true, alias: "markdown", required: false }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: false }] }], currentActorId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentActorId", required: false }] }], canDecide: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDecide", required: false }] }], decisionOwner: [{ type: i0.Input, args: [{ isSignal: true, alias: "decisionOwner", required: false }] }], approvalDecision: [{ type: i0.Output, args: ["approvalDecision"] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkVirtualScrollViewport), { isSignal: true }] }] } });
6768
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article\n class=\"message\"\n [attr.data-role]=\"message.role\"\n [attr.data-others]=\"isOthers(message) ? 'true' : null\"\n [attr.data-author]=\"isTrigger(message) ? 'trigger' : isRule(message) ? 'rule' : null\">\n @if (isTrigger(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.trigger' | translate }}</p>\n } @else if (isRule(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.rule' | translate }}</p>\n } @else if (isOthers(message)) {\n <p class=\"author\">{{ authorName(message) ?? ('nh-assistant.thread.participant' | translate) }}</p>\n } @else {\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n }\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"rightFor(group.part).canDecide\"\n [decisionOwner]=\"rightFor(group.part).decisionOwner\"\n [waitingFor]=\"rightFor(group.part).waitingFor\"\n [canGiveFeedback]=\"rightFor(group.part).canGiveFeedback === true\"\n (decide)=\"choose(group.part, $event)\" />\n }\n }\n }\n </article>\n</ng-template>\n\n<ng-template #workingTemplate>\n <p class=\"working\" role=\"status\">\n <span class=\"dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span>\n {{ 'nh-assistant.thread.working' | translate }}\n @if (elapsedSeconds() > 0) {\n <span class=\"elapsed\">{{ 'nh-assistant.thread.elapsed' | translate: { seconds: elapsedSeconds() } }}</span>\n }\n </p>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.log{display:flex;flex:1;flex-direction:column;min-height:0}.scroller{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px;outline:none}.scroller:focus-visible{box-shadow:inset 0 0 0 2px var(--nha-focus)}cdk-virtual-scroll-viewport.scroller{height:100%}.row+.row{margin-top:20px}.message{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0}.message[data-role=user]{align-items:flex-end}.author{margin:0;color:var(--nha-text-muted);font-size:11px;font-weight:600}.bubble{max-width:100%;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}.plain{white-space:pre-wrap}.message[data-role=user] .bubble{max-width:88%;color:var(--nha-user-text);background:var(--nha-user-bubble)}.message[data-others=true]{align-items:flex-start}.message[data-others=true] .bubble{color:var(--nha-text);background:var(--nha-surface-strong)}.message[data-author=trigger]{align-items:stretch}.message[data-author=trigger] .bubble{max-width:none;color:var(--nha-text-muted);background:transparent;border:1px dashed var(--nha-border)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.part{width:100%}:host ::ng-deep .markdown :first-child{margin-top:0}:host ::ng-deep .markdown :last-child{margin-bottom:0}:host ::ng-deep .markdown p,:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol,:host ::ng-deep .markdown pre,:host ::ng-deep .markdown blockquote,:host ::ng-deep .markdown table{margin:0 0 8px}:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol{padding-left:20px}:host ::ng-deep .markdown h1,:host ::ng-deep .markdown h2,:host ::ng-deep .markdown h3,:host ::ng-deep .markdown h4,:host ::ng-deep .markdown h5,:host ::ng-deep .markdown h6{margin:10px 0 6px;font-size:15px;line-height:1.3}:host ::ng-deep .markdown a{color:var(--nha-highlight);text-decoration:underline}:host ::ng-deep .markdown a:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host ::ng-deep .markdown code{border-radius:6px;padding:1px 4px;background:var(--nha-surface-strong);font-family:var(--nha-mono);font-size:12.5px}:host ::ng-deep .markdown pre{overflow-x:auto;border-radius:8px;padding:8px;background:var(--nha-surface-strong)}:host ::ng-deep .markdown pre code{padding:0;background:none}:host ::ng-deep .markdown blockquote{border-left:3px solid var(--nha-border);padding-left:10px;color:var(--nha-text-muted)}:host ::ng-deep .markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;font-size:13px}:host ::ng-deep .markdown th,:host ::ng-deep .markdown td{border:1px solid var(--nha-border);padding:4px 8px;text-align:left}.working{display:flex;align-items:center;gap:8px;margin:14px 0 0;color:var(--nha-text-muted);font-size:13px}.elapsed{font-variant-numeric:tabular-nums}.dots{display:inline-flex;gap:3px}.dots span{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.6}@media(prefers-reduced-motion:no-preference){.dots span{animation:nha-bounce 1s ease-in-out infinite}.dots span:nth-child(2){animation-delay:.15s}.dots span:nth-child(3){animation-delay:.3s}}@keyframes nha-bounce{0%,80%,to{transform:translateY(0);opacity:.35}40%{transform:translateY(-3px);opacity:1}}\n"] }]
6769
+ }], ctorParameters: () => [], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: true }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], markdown: [{ type: i0.Input, args: [{ isSignal: true, alias: "markdown", required: false }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: false }] }], currentActorId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentActorId", required: false }] }], canDecide: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDecide", required: false }] }], decisionOwner: [{ type: i0.Input, args: [{ isSignal: true, alias: "decisionOwner", required: false }] }], approvalRights: [{ type: i0.Input, args: [{ isSignal: true, alias: "approvalRights", required: false }] }], approvalDecision: [{ type: i0.Output, args: ["approvalDecision"] }], approvalChoice: [{ type: i0.Output, args: ["approvalChoice"] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkVirtualScrollViewport), { isSignal: true }] }] } });
5091
6770
 
5092
6771
  let nextId = 0;
5093
6772
  /**
@@ -5300,11 +6979,15 @@ class NhAssistantPanelComponent {
5300
6979
  decide(decision) {
5301
6980
  this.store.decide(decision);
5302
6981
  }
6982
+ /** Decides the approval of a card; in an agent thread several proposals can be open. */
6983
+ decideApproval(choice) {
6984
+ this.store.decide(choice.decision, undefined, choice.approvalId);
6985
+ }
5303
6986
  cancel() {
5304
6987
  void this.store.cancel();
5305
6988
  }
5306
6989
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5307
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantPanelComponent, isStandalone: true, selector: "nh-assistant-panel", viewQueries: [{ propertyName: "drawer", first: true, predicate: ["drawer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n @if (store.enabled()) {\n <nh-assistant-activity [items]=\"store.activity()\" (conversationSelect)=\"openConversation($event)\" />\n }\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showShare()) {\n <nh-assistant-share class=\"overlay-view\" [section]=\"shareSection()\" (closed)=\"showShare.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\n [messages]=\"messages()\"\n [streaming]=\"busy()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n [members]=\"store.members()\"\n [currentActorId]=\"conversation()?.currentActorId ?? null\"\n [canDecide]=\"store.canDecide()\"\n [decisionOwner]=\"activeMemberName()\"\n (approvalDecision)=\"decide($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError(); notificationError.set(null)\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences() && !showShare()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\n type=\"button\"\n [attr.aria-pressed]=\"!store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon name=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\">\n @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantActivityComponent, selector: "nh-assistant-activity", inputs: ["items"], outputs: ["conversationSelect"] }, { kind: "component", type: NhAssistantAgentPickerComponent, selector: "nh-assistant-agent-picker", inputs: ["agents", "selectedAgentId", "disabled", "compact"], outputs: ["agentChange"] }, { kind: "component", type: NhAssistantComposerComponent, selector: "nh-assistant-composer", inputs: ["disabled", "sendDisabled", "busy", "queueWhileBusy", "status", "statusActor", "canStop", "maxLength", "restore"], outputs: ["send", "cancel", "restored"] }, { kind: "component", type: NhAssistantConversationListComponent, selector: "nh-assistant-conversation-list", inputs: ["conversations", "total", "activeConversationId", "loading", "disabled"], outputs: ["conversationSelect", "conversationDelete", "conversationCreate"] }, { kind: "component", type: NhAssistantPreferencesComponent, selector: "nh-assistant-preferences", outputs: ["closed", "saved"] }, { kind: "component", type: NhAssistantShareComponent, selector: "nh-assistant-share", inputs: ["section"], outputs: ["closed"] }, { kind: "component", type: NhAssistantThreadComponent, selector: "nh-assistant-thread", inputs: ["messages", "streaming", "deciding", "markdown", "members", "currentActorId", "canDecide", "decisionOwner"], outputs: ["approvalDecision"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6990
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantPanelComponent, isStandalone: true, selector: "nh-assistant-panel", viewQueries: [{ propertyName: "drawer", first: true, predicate: ["drawer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n @if (store.enabled()) {\n <nh-assistant-activity [items]=\"store.activity()\" (conversationSelect)=\"openConversation($event)\" />\n }\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showShare()) {\n <nh-assistant-share class=\"overlay-view\" [section]=\"shareSection()\" (closed)=\"showShare.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\n [messages]=\"messages()\"\n [streaming]=\"busy()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n [members]=\"store.members()\"\n [currentActorId]=\"conversation()?.currentActorId ?? null\"\n [canDecide]=\"store.canDecide()\"\n [decisionOwner]=\"activeMemberName()\"\n [approvalRights]=\"store.approvalRights()\"\n (approvalChoice)=\"decideApproval($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError(); notificationError.set(null)\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences() && !showShare()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\n type=\"button\"\n [attr.aria-pressed]=\"!store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon name=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\">\n @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantActivityComponent, selector: "nh-assistant-activity", inputs: ["items"], outputs: ["conversationSelect"] }, { kind: "component", type: NhAssistantAgentPickerComponent, selector: "nh-assistant-agent-picker", inputs: ["agents", "selectedAgentId", "disabled", "compact"], outputs: ["agentChange"] }, { kind: "component", type: NhAssistantComposerComponent, selector: "nh-assistant-composer", inputs: ["disabled", "sendDisabled", "busy", "queueWhileBusy", "status", "statusActor", "canStop", "maxLength", "restore"], outputs: ["send", "cancel", "restored"] }, { kind: "component", type: NhAssistantConversationListComponent, selector: "nh-assistant-conversation-list", inputs: ["conversations", "total", "activeConversationId", "loading", "disabled"], outputs: ["conversationSelect", "conversationDelete", "conversationCreate"] }, { kind: "component", type: NhAssistantPreferencesComponent, selector: "nh-assistant-preferences", outputs: ["closed", "saved"] }, { kind: "component", type: NhAssistantShareComponent, selector: "nh-assistant-share", inputs: ["section"], outputs: ["closed"] }, { kind: "component", type: NhAssistantThreadComponent, selector: "nh-assistant-thread", inputs: ["messages", "streaming", "deciding", "markdown", "members", "currentActorId", "canDecide", "decisionOwner", "approvalRights"], outputs: ["approvalDecision", "approvalChoice"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5308
6991
  }
5309
6992
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPanelComponent, decorators: [{
5310
6993
  type: Component,
@@ -5320,22 +7003,96 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
5320
7003
  NhAssistantPreferencesComponent,
5321
7004
  NhAssistantShareComponent,
5322
7005
  NhAssistantThreadComponent
5323
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n @if (store.enabled()) {\n <nh-assistant-activity [items]=\"store.activity()\" (conversationSelect)=\"openConversation($event)\" />\n }\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showShare()) {\n <nh-assistant-share class=\"overlay-view\" [section]=\"shareSection()\" (closed)=\"showShare.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\n [messages]=\"messages()\"\n [streaming]=\"busy()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n [members]=\"store.members()\"\n [currentActorId]=\"conversation()?.currentActorId ?? null\"\n [canDecide]=\"store.canDecide()\"\n [decisionOwner]=\"activeMemberName()\"\n (approvalDecision)=\"decide($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError(); notificationError.set(null)\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences() && !showShare()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\n type=\"button\"\n [attr.aria-pressed]=\"!store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon name=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\">\n @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"] }]
7006
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n @if (store.enabled()) {\n <nh-assistant-activity [items]=\"store.activity()\" (conversationSelect)=\"openConversation($event)\" />\n }\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showShare()) {\n <nh-assistant-share class=\"overlay-view\" [section]=\"shareSection()\" (closed)=\"showShare.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\n [messages]=\"messages()\"\n [streaming]=\"busy()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n [members]=\"store.members()\"\n [currentActorId]=\"conversation()?.currentActorId ?? null\"\n [canDecide]=\"store.canDecide()\"\n [decisionOwner]=\"activeMemberName()\"\n [approvalRights]=\"store.approvalRights()\"\n (approvalChoice)=\"decideApproval($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError(); notificationError.set(null)\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences() && !showShare()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\n type=\"button\"\n [attr.aria-pressed]=\"!store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon name=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\">\n @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"] }]
5324
7007
  }], ctorParameters: () => [], propDecorators: { drawer: [{ type: i0.ViewChild, args: ['drawer', { isSignal: true }] }] } });
5325
7008
 
5326
- /** Converts any failure into a user-facing code and translation key, never raw text. */
5327
- function toNhAssistantError(error) {
5328
- if (error instanceof NhAssistantApiError) {
5329
- return { code: error.code, messageKey: error.messageKey };
7009
+ /**
7010
+ * A compact count of the agent work on one subject, for example on each row of a project list.
7011
+ * It shows nothing while the subject has no work. Otherwise it shows the number of proposals the
7012
+ * viewer may decide in the accent color, or, when they may decide none, a neutral count of the
7013
+ * open proposals, and a small working indicator while an agent works on the subject. Its tooltip
7014
+ * and accessible name say both counts. Badges on one page share one summary request per tick and
7015
+ * refresh on live agent-work updates of their subject; see `NhAssistantSubjectSummaryService`.
7016
+ */
7017
+ class NhAssistantSubjectBadgeComponent {
7018
+ constructor() {
7019
+ this.summaries = inject(NhAssistantSubjectSummaryService);
7020
+ this.summary = signal(null, ...(ngDevMode ? [{ debugName: "summary" }] : []));
7021
+ /** The application's kind of subject, for example `project`. */
7022
+ this.subjectType = input.required(...(ngDevMode ? [{ debugName: "subjectType" }] : []));
7023
+ this.subjectId = input.required(...(ngDevMode ? [{ debugName: "subjectId" }] : []));
7024
+ /** The summary while the subject has work to show; `null` otherwise. */
7025
+ this.work = computed(() => {
7026
+ const summary = this.summary();
7027
+ return summary && (summary.openProposals > 0 || summary.runActive) ? summary : null;
7028
+ }, ...(ngDevMode ? [{ debugName: "work" }] : []));
7029
+ this.count = computed(() => {
7030
+ const summary = this.work();
7031
+ if (!summary) {
7032
+ return 0;
7033
+ }
7034
+ return summary.proposalsToDecide > 0 ? summary.proposalsToDecide : summary.openProposals;
7035
+ }, ...(ngDevMode ? [{ debugName: "count" }] : []));
7036
+ this.countLabel = computed(() => this.count() > 99 ? '99+' : String(this.count()), ...(ngDevMode ? [{ debugName: "countLabel" }] : []));
7037
+ this.proposalsKey = computed(() => {
7038
+ const summary = this.work();
7039
+ if (!summary || summary.openProposals === 0) {
7040
+ return null;
7041
+ }
7042
+ return summary.proposalsToDecide > 0 ? 'nh-assistant.subject-badge.to-decide' : 'nh-assistant.subject-badge.open';
7043
+ }, ...(ngDevMode ? [{ debugName: "proposalsKey" }] : []));
7044
+ effect(onCleanup => {
7045
+ const subjectType = this.subjectType();
7046
+ const subjectId = this.subjectId();
7047
+ const subscription = untracked(() => this.summaries.summary(subjectType, subjectId).subscribe(summary => this.summary.set(summary)));
7048
+ onCleanup(() => subscription.unsubscribe());
7049
+ });
5330
7050
  }
5331
- return { code: NhAssistantClientErrorCodes.server, messageKey: nhAssistantErrorMessageKey(NhAssistantClientErrorCodes.server) };
5332
- }
5333
- /** Translation keys for an error, from the most to the least specific. */
5334
- function nhAssistantErrorKeys(error) {
5335
- return error ? [error.messageKey, nhAssistantErrorMessageKey(error.code), 'nh-assistant.errors.generic'] : [];
7051
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantSubjectBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7052
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantSubjectBadgeComponent, isStandalone: true, selector: "nh-assistant-subject-badge", inputs: { subjectType: { classPropertyName: "subjectType", publicName: "subjectType", isSignal: true, isRequired: true, transformFunction: null }, subjectId: { classPropertyName: "subjectId", publicName: "subjectId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
7053
+ @if (work(); as summary) {
7054
+ @let proposals = proposalsKey() ? (proposalsKey()! | translate: { toDecide: summary.proposalsToDecide, open: summary.openProposals }) : '';
7055
+ @let working = summary.runActive ? ('nh-assistant.subject-badge.working' | translate) : '';
7056
+ @let label = proposals && working ? proposals + ' ' + working : proposals || working;
7057
+ <span
7058
+ class="subject-badge"
7059
+ [attr.data-state]="summary.proposalsToDecide > 0 ? 'decide' : summary.openProposals > 0 ? 'open' : 'working'"
7060
+ [attr.title]="label">
7061
+ <nh-assistant-icon name="assistant" />
7062
+ @if (count() > 0) {
7063
+ <span class="count" aria-hidden="true">{{ countLabel() }}</span>
7064
+ }
7065
+ @if (summary.runActive) {
7066
+ <span class="working" aria-hidden="true"></span>
7067
+ }
7068
+ <span class="visually-hidden">{{ label }}</span>
7069
+ </span>
7070
+ }
7071
+ `, isInline: true, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:inline-flex;vertical-align:middle}.subject-badge{position:relative;display:inline-flex;align-items:center;gap:4px;min-height:22px;border:1px solid var(--nha-border);border-radius:999px;padding:1px 8px 1px 6px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-family:var(--nha-font);font-size:12px;font-weight:700;line-height:1;white-space:nowrap}.subject-badge nh-assistant-icon{font-size:13px}.subject-badge[data-state=decide]{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.subject-badge[data-state=working]{padding-right:6px}.count{font-variant-numeric:tabular-nums}.working{width:8px;height:8px;border:2px solid color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);border-radius:50%;animation:nh-assistant-subject-spin .9s linear infinite}.subject-badge[data-state=decide] .working{border-color:color-mix(in srgb,var(--nha-accent-contrast) 35%,transparent);border-top-color:var(--nha-accent-contrast)}@media(prefers-reduced-motion:reduce){.working{border-color:var(--nha-highlight);animation:none}.subject-badge[data-state=decide] .working{border-color:var(--nha-accent-contrast)}}@keyframes nh-assistant-subject-spin{to{transform:rotate(360deg)}}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5336
7072
  }
5337
- /** Dash-case identifiers as the contract requires for agent and MCP server ids. */
5338
- const NH_ASSISTANT_DASH_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7073
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantSubjectBadgeComponent, decorators: [{
7074
+ type: Component,
7075
+ args: [{ selector: 'nh-assistant-subject-badge', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
7076
+ @if (work(); as summary) {
7077
+ @let proposals = proposalsKey() ? (proposalsKey()! | translate: { toDecide: summary.proposalsToDecide, open: summary.openProposals }) : '';
7078
+ @let working = summary.runActive ? ('nh-assistant.subject-badge.working' | translate) : '';
7079
+ @let label = proposals && working ? proposals + ' ' + working : proposals || working;
7080
+ <span
7081
+ class="subject-badge"
7082
+ [attr.data-state]="summary.proposalsToDecide > 0 ? 'decide' : summary.openProposals > 0 ? 'open' : 'working'"
7083
+ [attr.title]="label">
7084
+ <nh-assistant-icon name="assistant" />
7085
+ @if (count() > 0) {
7086
+ <span class="count" aria-hidden="true">{{ countLabel() }}</span>
7087
+ }
7088
+ @if (summary.runActive) {
7089
+ <span class="working" aria-hidden="true"></span>
7090
+ }
7091
+ <span class="visually-hidden">{{ label }}</span>
7092
+ </span>
7093
+ }
7094
+ `, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:inline-flex;vertical-align:middle}.subject-badge{position:relative;display:inline-flex;align-items:center;gap:4px;min-height:22px;border:1px solid var(--nha-border);border-radius:999px;padding:1px 8px 1px 6px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-family:var(--nha-font);font-size:12px;font-weight:700;line-height:1;white-space:nowrap}.subject-badge nh-assistant-icon{font-size:13px}.subject-badge[data-state=decide]{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.subject-badge[data-state=working]{padding-right:6px}.count{font-variant-numeric:tabular-nums}.working{width:8px;height:8px;border:2px solid color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);border-radius:50%;animation:nh-assistant-subject-spin .9s linear infinite}.subject-badge[data-state=decide] .working{border-color:color-mix(in srgb,var(--nha-accent-contrast) 35%,transparent);border-top-color:var(--nha-accent-contrast)}@media(prefers-reduced-motion:reduce){.working{border-color:var(--nha-highlight);animation:none}.subject-badge[data-state=decide] .working{border-color:var(--nha-accent-contrast)}}@keyframes nh-assistant-subject-spin{to{transform:rotate(360deg)}}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
7095
+ }], ctorParameters: () => [], propDecorators: { subjectType: [{ type: i0.Input, args: [{ isSignal: true, alias: "subjectType", required: true }] }], subjectId: [{ type: i0.Input, args: [{ isSignal: true, alias: "subjectId", required: true }] }] } });
5339
7096
 
5340
7097
  /*
5341
7098
  * Public API surface of @newheap/platform-ai-chat.
@@ -5345,5 +7102,5 @@ const NH_ASSISTANT_DASH_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
5345
7102
  * Generated bundle index. Do not edit.
5346
7103
  */
5347
7104
 
5348
- export { NH_ASSISTANT_ACCESS_POLICY, NH_ASSISTANT_CONFIG, NH_ASSISTANT_CONVERSATION_FRAGMENT, NH_ASSISTANT_FETCH, NH_ASSISTANT_ICONS, NH_ASSISTANT_JOIN_FRAGMENT, NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS, NH_ASSISTANT_NO_ANSWER_NOTICE_CODE, NH_ASSISTANT_PAGE_CONTEXT_LIMITS, NH_ASSISTANT_PANEL_MOBILE_QUERY, NH_ASSISTANT_PANEL_WIDTH, NH_ASSISTANT_SSE_EVENT_TYPES, NH_ASSISTANT_TRANSLATIONS, NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, NhAssistantActivityComponent, NhAssistantAdminApiService, NhAssistantAgentPickerComponent, NhAssistantAllowAllAccessPolicy, NhAssistantApiError, NhAssistantApiService, NhAssistantApprovalCardComponent, NhAssistantClientErrorCodes, NhAssistantComposerComponent, NhAssistantConversationListComponent, NhAssistantLauncherComponent, NhAssistantLiveService, NhAssistantPanelComponent, NhAssistantPanelService, NhAssistantPreferencesComponent, NhAssistantPushService, NhAssistantShareComponent, NhAssistantSseParser, NhAssistantStore, NhAssistantThreadComponent, NhAssistantToolCallCardComponent, NhAssistantToolCallGroupComponent, NhAssistantTranslationMerger, applyNhAssistantApprovalDecision, applyNhAssistantConversationChange, applyNhAssistantEvent, applyNhAssistantLiveEvent, describeNhAssistantClientContext, formatNhAssistantCountdown, nhAssistantCodeForStatus, nhAssistantErrorMessageKey, nhAssistantIsUnread, nhAssistantLatestTurnHasText, nhAssistantShareLink, normalizeNhAssistantClientContext, provideNhAssistant, NH_ASSISTANT_DASH_CASE as ɵNH_ASSISTANT_DASH_CASE, NhAssistantIconComponent as ɵNhAssistantIconComponent, NhAssistantTranslatePipe as ɵNhAssistantTranslatePipe, nhAssistantErrorKeys as ɵnhAssistantErrorKeys, toNhAssistantError as ɵtoNhAssistantError };
7105
+ export { NH_ASSISTANT_ACCESS_POLICY, NH_ASSISTANT_CONFIG, NH_ASSISTANT_CONVERSATION_FRAGMENT, NH_ASSISTANT_FEEDBACK_CATEGORIES, NH_ASSISTANT_FEEDBACK_GROUPS, NH_ASSISTANT_FETCH, NH_ASSISTANT_ICONS, NH_ASSISTANT_JOIN_FRAGMENT, NH_ASSISTANT_MAX_BULK_FEEDBACK, NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS, NH_ASSISTANT_MAX_FEEDBACK_TEXT, NH_ASSISTANT_MAX_PROPOSAL_LOOKUP, NH_ASSISTANT_MAX_SUBJECT_SUMMARY, NH_ASSISTANT_NO_ANSWER_NOTICE_CODE, NH_ASSISTANT_PAGE_CONTEXT_LIMITS, NH_ASSISTANT_PANEL_MOBILE_QUERY, NH_ASSISTANT_PANEL_WIDTH, NH_ASSISTANT_SSE_EVENT_TYPES, NH_ASSISTANT_TRANSLATIONS, NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, NhAssistantActivityComponent, NhAssistantAdminApiService, NhAssistantAgentPickerComponent, NhAssistantAgentRunApiService, NhAssistantAllowAllAccessPolicy, NhAssistantApiError, NhAssistantApiService, NhAssistantApprovalCardComponent, NhAssistantClientErrorCodes, NhAssistantComposerComponent, NhAssistantConversationListComponent, NhAssistantLauncherComponent, NhAssistantLiveService, NhAssistantPanelComponent, NhAssistantPanelService, NhAssistantPreferencesComponent, NhAssistantProposalFeedbackComponent, NhAssistantPushService, NhAssistantShareComponent, NhAssistantSseParser, NhAssistantStore, NhAssistantSubjectBadgeComponent, NhAssistantSubjectSummaryService, NhAssistantThreadComponent, NhAssistantToolCallCardComponent, NhAssistantToolCallGroupComponent, NhAssistantTranslationMerger, applyNhAssistantApprovalDecision, applyNhAssistantConversationChange, applyNhAssistantEvent, applyNhAssistantLiveEvent, describeNhAssistantClientContext, formatNhAssistantCountdown, nhAssistantCodeForStatus, nhAssistantErrorMessageKey, nhAssistantIsUnread, nhAssistantLatestTurnHasText, nhAssistantShareLink, normalizeNhAssistantClientContext, provideNhAssistant, NH_ASSISTANT_DASH_CASE as ɵNH_ASSISTANT_DASH_CASE, NhAssistantIconComponent as ɵNhAssistantIconComponent, NhAssistantTranslatePipe as ɵNhAssistantTranslatePipe, nhAssistantErrorKeys as ɵnhAssistantErrorKeys, nhAssistantReloadOnAgentWork as ɵnhAssistantReloadOnAgentWork, toNhAssistantError as ɵtoNhAssistantError };
5349
7106
  //# sourceMappingURL=newheap-platform-ai-chat.mjs.map