@newheap/platform-ai-chat 0.12.0 → 0.14.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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, signal, computed, DestroyRef, ChangeDetectionStrategy, Component, input, output, effect, untracked, Injector, ElementRef, viewChild, afterNextRender } from '@angular/core';
2
+ import { inject, signal, computed, DestroyRef, ChangeDetectionStrategy, Component, input, output, effect, untracked, Injector, ElementRef, afterNextRender, viewChild } from '@angular/core';
3
3
  import { TranslatePipe } from '@ngx-translate/core';
4
4
  import { NhAssistantAdminApiService, ɵnhAssistantErrorKeys as _nhAssistantErrorKeys, ɵtoNhAssistantError as _toNhAssistantError, NhAssistantClientErrorCodes, ɵNhAssistantIconComponent as _NhAssistantIconComponent, ɵNhAssistantTranslatePipe as _NhAssistantTranslatePipe, ɵNH_ASSISTANT_DASH_CASE as _NH_ASSISTANT_DASH_CASE, NhAssistantPanelService, NH_ASSISTANT_FEEDBACK_CATEGORIES, NhAssistantStore, nhAssistantErrorMessageKey } from '@newheap/platform-ai-chat';
5
5
  import { DatePipe, DOCUMENT, DecimalPipe, PercentPipe } from '@angular/common';
@@ -7,7 +7,7 @@ import { forkJoin, Subscription } from 'rxjs';
7
7
 
8
8
  /** Maximum length of the application context and of agent instructions. */
9
9
  const NH_ASSISTANT_MAX_INSTRUCTIONS = 20_000;
10
- let nextId$8 = 0;
10
+ let nextId$9 = 0;
11
11
  /**
12
12
  * Editor of the application context: the current version with its hash, optimistic
13
13
  * concurrency through `expectedVersion`, and the version history.
@@ -15,7 +15,7 @@ let nextId$8 = 0;
15
15
  class NhAssistantAdminContextComponent {
16
16
  constructor() {
17
17
  this.admin = inject(NhAssistantAdminApiService);
18
- this.id = `nh-assistant-admin-context-${nextId$8++}`;
18
+ this.id = `nh-assistant-admin-context-${nextId$9++}`;
19
19
  this.max = NH_ASSISTANT_MAX_INSTRUCTIONS;
20
20
  this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
21
21
  this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
@@ -101,7 +101,7 @@ function nhAssistantSelectorMatches(toolId, selector) {
101
101
  return new RegExp(`^${pattern}$`).test(toolId);
102
102
  }
103
103
  const selectorPattern = /^[a-z0-9*][a-z0-9.*_-]*$/i;
104
- let nextId$7 = 0;
104
+ let nextId$8 = 0;
105
105
  /**
106
106
  * Creates or edits one agent: texts, autonomy, policy, tool selectors chosen from the tool
107
107
  * catalog or typed as patterns, and assigned MCP servers.
@@ -115,7 +115,7 @@ class NhAssistantAdminAgentEditorComponent {
115
115
  this.servers = input([], ...(ngDevMode ? [{ debugName: "servers" }] : []));
116
116
  this.saved = output();
117
117
  this.cancelled = output();
118
- this.id = `nh-assistant-agent-editor-${nextId$7++}`;
118
+ this.id = `nh-assistant-agent-editor-${nextId$8++}`;
119
119
  this.max = NH_ASSISTANT_MAX_INSTRUCTIONS;
120
120
  this.autonomyLevels = ['observe', 'explain', 'propose', 'simulate', 'execute'];
121
121
  this.agentId = signal('', ...(ngDevMode ? [{ debugName: "agentId" }] : []));
@@ -245,7 +245,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
245
245
  const dayMs$1 = 24 * 60 * 60 * 1000;
246
246
  const defaultReplayRuns = 100;
247
247
  const maxReplayRuns = 500;
248
- let nextId$6 = 0;
248
+ let nextId$7 = 0;
249
249
  /**
250
250
  * Compares a shadow variant of an agent with the live agent, for the agent's administrators: the
251
251
  * runs that handled the same events, per tool the proposals both made, only one made and what
@@ -263,7 +263,7 @@ class NhAssistantAgentEvaluationComponent {
263
263
  this.agentId = input.required(...(ngDevMode ? [{ debugName: "agentId" }] : []));
264
264
  /** Its shadow variants; the first is compared until another is chosen. */
265
265
  this.variants = input.required(...(ngDevMode ? [{ debugName: "variants" }] : []));
266
- this.id = `nh-assistant-agent-evaluation-${nextId$6++}`;
266
+ this.id = `nh-assistant-agent-evaluation-${nextId$7++}`;
267
267
  this.periods = [7, 30, 90];
268
268
  this.maxReplayRuns = maxReplayRuns;
269
269
  this.canOpenThreads = this.panel !== null;
@@ -425,7 +425,7 @@ function periodRange$1(days) {
425
425
 
426
426
  const dayMs = 24 * 60 * 60 * 1000;
427
427
  const topCategoryCount = 3;
428
- let nextId$5 = 0;
428
+ let nextId$6 = 0;
429
429
  /**
430
430
  * What people said when they rejected an agent's proposals, for the agent's administrators: per
431
431
  * tool and agent version the decisions, the rejection rate, the share of rejections with feedback,
@@ -440,7 +440,7 @@ class NhAssistantAgentFeedbackReportComponent {
440
440
  this.agentId = input.required(...(ngDevMode ? [{ debugName: "agentId" }] : []));
441
441
  /** Feedback per page; the server allows at most 100. */
442
442
  this.itemsPerPage = input(20, ...(ngDevMode ? [{ debugName: "itemsPerPage" }] : []));
443
- this.id = `nh-assistant-agent-feedback-${nextId$5++}`;
443
+ this.id = `nh-assistant-agent-feedback-${nextId$6++}`;
444
444
  this.periods = [7, 30, 90];
445
445
  this.categories = NH_ASSISTANT_FEEDBACK_CATEGORIES;
446
446
  this.period = signal(30, ...(ngDevMode ? [{ debugName: "period" }] : []));
@@ -630,15 +630,427 @@ function invalidatedKinds(row) {
630
630
  .sort((left, right) => right.count - left.count);
631
631
  }
632
632
 
633
+ let nextId$5 = 0;
634
+ /** The longest reason the server stores with a change. */
635
+ const maxReason = 500;
636
+ const maxDescription = 300;
637
+ const maxInstructions = 20_000;
638
+ const maxMessage = 2_000;
639
+ const dashCase = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
640
+ /**
641
+ * The run configuration of one agent as data: the run settings of an agent created in the
642
+ * administration, its skills and triggers in code (read only) and as data, and the data that no
643
+ * longer fits the code. Administrators add and change data skills, live at once or as a candidate in
644
+ * shadow that they compare with the live version and promote or discard; they add data triggers on
645
+ * the application's events or on a schedule and put them live. New skills and triggers start in
646
+ * shadow; `changed` tells the agents page to reload the agent's skill states.
647
+ */
648
+ class NhAssistantAgentRunConfigurationComponent {
649
+ constructor() {
650
+ this.admin = inject(NhAssistantAdminApiService);
651
+ this.injector = inject(Injector);
652
+ this.host = inject(ElementRef);
653
+ this.agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
654
+ /** A change of the run configuration changed the agent's skills or state. */
655
+ this.changed = output();
656
+ this.id = `nh-assistant-run-configuration-${nextId$5++}`;
657
+ this.maxReason = maxReason;
658
+ this.maxDescription = maxDescription;
659
+ this.maxInstructions = maxInstructions;
660
+ this.maxMessage = maxMessage;
661
+ this.accountabilities = ['agent', 'subject-responsible', 'service-account'];
662
+ this.elevatedReadModes = ['restricted', 'deny'];
663
+ this.conflicts = ['coalesce', 'queue', 'supersede', 'ignore'];
664
+ this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
665
+ this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
666
+ this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
667
+ this.catalog = signal(null, ...(ngDevMode ? [{ debugName: "catalog" }] : []));
668
+ this.configuration = signal(null, ...(ngDevMode ? [{ debugName: "configuration" }] : []));
669
+ this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
670
+ this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
671
+ this.saved = signal(null, ...(ngDevMode ? [{ debugName: "saved" }] : []));
672
+ this.form = signal(null, ...(ngDevMode ? [{ debugName: "form" }] : []));
673
+ this.confirming = signal(null, ...(ngDevMode ? [{ debugName: "confirming" }] : []));
674
+ this.reason = signal('', ...(ngDevMode ? [{ debugName: "reason" }] : []));
675
+ /** The skill being changed; `null` with the skill form open adds a new skill. */
676
+ this.editingSkill = signal(null, ...(ngDevMode ? [{ debugName: "editingSkill" }] : []));
677
+ /** The trigger being changed; `null` with the trigger form open adds a new trigger. */
678
+ this.editingTrigger = signal(null, ...(ngDevMode ? [{ debugName: "editingTrigger" }] : []));
679
+ this.comparison = signal(null, ...(ngDevMode ? [{ debugName: "comparison" }] : []));
680
+ this.versions = signal(null, ...(ngDevMode ? [{ debugName: "versions" }] : []));
681
+ // Run settings form.
682
+ this.accountability = signal('service-account', ...(ngDevMode ? [{ debugName: "accountability" }] : []));
683
+ this.serviceAccount = signal('', ...(ngDevMode ? [{ debugName: "serviceAccount" }] : []));
684
+ this.approvalPolicy = signal('', ...(ngDevMode ? [{ debugName: "approvalPolicy" }] : []));
685
+ this.ruleHandler = signal('', ...(ngDevMode ? [{ debugName: "ruleHandler" }] : []));
686
+ this.triageProfile = signal('', ...(ngDevMode ? [{ debugName: "triageProfile" }] : []));
687
+ this.retryCount = signal('3', ...(ngDevMode ? [{ debugName: "retryCount" }] : []));
688
+ this.maxConcurrentRuns = signal('4', ...(ngDevMode ? [{ debugName: "maxConcurrentRuns" }] : []));
689
+ this.maxRunsPerSubjectPerDay = signal('10', ...(ngDevMode ? [{ debugName: "maxRunsPerSubjectPerDay" }] : []));
690
+ this.maxTokensPerRun = signal('', ...(ngDevMode ? [{ debugName: "maxTokensPerRun" }] : []));
691
+ this.dailyToolCalls = signal('', ...(ngDevMode ? [{ debugName: "dailyToolCalls" }] : []));
692
+ this.dailyTokens = signal('', ...(ngDevMode ? [{ debugName: "dailyTokens" }] : []));
693
+ this.allowGatewayReads = signal(false, ...(ngDevMode ? [{ debugName: "allowGatewayReads" }] : []));
694
+ this.delegateFromInitiator = signal(false, ...(ngDevMode ? [{ debugName: "delegateFromInitiator" }] : []));
695
+ this.elevatedReads = signal('restricted', ...(ngDevMode ? [{ debugName: "elevatedReads" }] : []));
696
+ // Skill form.
697
+ this.skillId = signal('', ...(ngDevMode ? [{ debugName: "skillId" }] : []));
698
+ this.skillDescription = signal('', ...(ngDevMode ? [{ debugName: "skillDescription" }] : []));
699
+ this.skillInstructions = signal('', ...(ngDevMode ? [{ debugName: "skillInstructions" }] : []));
700
+ this.skillTools = signal([], ...(ngDevMode ? [{ debugName: "skillTools" }] : []));
701
+ this.skillProfile = signal('', ...(ngDevMode ? [{ debugName: "skillProfile" }] : []));
702
+ this.skillRuleHandler = signal('', ...(ngDevMode ? [{ debugName: "skillRuleHandler" }] : []));
703
+ this.skillApprovalPolicy = signal('', ...(ngDevMode ? [{ debugName: "skillApprovalPolicy" }] : []));
704
+ this.skillMaxTokens = signal('', ...(ngDevMode ? [{ debugName: "skillMaxTokens" }] : []));
705
+ this.skillDailyToolCalls = signal('', ...(ngDevMode ? [{ debugName: "skillDailyToolCalls" }] : []));
706
+ this.skillDailyTokens = signal('', ...(ngDevMode ? [{ debugName: "skillDailyTokens" }] : []));
707
+ this.skillApply = signal('live', ...(ngDevMode ? [{ debugName: "skillApply" }] : []));
708
+ // Trigger form.
709
+ this.triggerId = signal('', ...(ngDevMode ? [{ debugName: "triggerId" }] : []));
710
+ this.triggerSource = signal('event', ...(ngDevMode ? [{ debugName: "triggerSource" }] : []));
711
+ this.triggerEvent = signal('', ...(ngDevMode ? [{ debugName: "triggerEvent" }] : []));
712
+ this.triggerCron = signal('', ...(ngDevMode ? [{ debugName: "triggerCron" }] : []));
713
+ this.triggerTimeZone = signal('', ...(ngDevMode ? [{ debugName: "triggerTimeZone" }] : []));
714
+ this.triggerSubjectSet = signal('', ...(ngDevMode ? [{ debugName: "triggerSubjectSet" }] : []));
715
+ this.triggerMessage = signal('', ...(ngDevMode ? [{ debugName: "triggerMessage" }] : []));
716
+ this.triggerSkill = signal('', ...(ngDevMode ? [{ debugName: "triggerSkill" }] : []));
717
+ this.triggerConflict = signal('coalesce', ...(ngDevMode ? [{ debugName: "triggerConflict" }] : []));
718
+ this.triggerDebounce = signal('120', ...(ngDevMode ? [{ debugName: "triggerDebounce" }] : []));
719
+ this.isAdminAgent = computed(() => this.configuration()?.source === 'admin', ...(ngDevMode ? [{ debugName: "isAdminAgent" }] : []));
720
+ /** An agent created in the administration gets skills and triggers once it has run settings. */
721
+ this.canAddRunData = computed(() => {
722
+ const configuration = this.configuration();
723
+ return configuration !== null && (configuration.source === 'code' || configuration.runSettings !== null);
724
+ }, ...(ngDevMode ? [{ debugName: "canAddRunData" }] : []));
725
+ this.skillOptions = computed(() => {
726
+ const configuration = this.configuration();
727
+ return configuration
728
+ ? [...configuration.codeSkills.map(skill => skill.skillId), ...configuration.dataSkills.map(skill => skill.skillId)]
729
+ : [];
730
+ }, ...(ngDevMode ? [{ debugName: "skillOptions" }] : []));
731
+ this.problems = computed(() => this.configuration()?.problems ?? [], ...(ngDevMode ? [{ debugName: "problems" }] : []));
732
+ this.skillIdInvalid = computed(() => this.editingSkill() === null && !dashCase.test(this.skillId().trim()), ...(ngDevMode ? [{ debugName: "skillIdInvalid" }] : []));
733
+ this.triggerIdInvalid = computed(() => this.editingTrigger() === null && !dashCase.test(this.triggerId().trim()), ...(ngDevMode ? [{ debugName: "triggerIdInvalid" }] : []));
734
+ this.descriptionInvalid = computed(() => {
735
+ const description = this.skillDescription().trim();
736
+ return description.length === 0 || description.length > maxDescription;
737
+ }, ...(ngDevMode ? [{ debugName: "descriptionInvalid" }] : []));
738
+ inject(DestroyRef).onDestroy(() => {
739
+ this.loadSubscription?.unsubscribe();
740
+ this.actionSubscription?.unsubscribe();
741
+ this.detailSubscription?.unsubscribe();
742
+ });
743
+ }
744
+ ngOnInit() {
745
+ this.load();
746
+ }
747
+ load() {
748
+ this.loading.set(true);
749
+ this.loadFailed.set(false);
750
+ this.loadSubscription?.unsubscribe();
751
+ this.loadSubscription = forkJoin([this.admin.getRunCatalog(), this.admin.getAgentConfiguration(this.agent().id)]).subscribe({
752
+ next: ([catalog, configuration]) => {
753
+ this.catalog.set(catalog);
754
+ this.configuration.set(configuration);
755
+ this.loading.set(false);
756
+ },
757
+ error: () => {
758
+ this.loading.set(false);
759
+ this.loadFailed.set(true);
760
+ }
761
+ });
762
+ }
763
+ /** The first error of a field from the last failed save, as a translation key. */
764
+ fieldError(field) {
765
+ const code = this.error()?.fieldErrors?.[field]?.[0];
766
+ return code ? `nh-assistant.admin.run-config.field-errors.${code}` : null;
767
+ }
768
+ editSettings() {
769
+ const settings = this.configuration()?.runSettings;
770
+ this.accountability.set(settings?.accountability ?? 'service-account');
771
+ this.serviceAccount.set(settings?.serviceAccountActorId ?? '');
772
+ this.approvalPolicy.set(settings?.approvalPolicy ?? '');
773
+ this.ruleHandler.set(settings?.ruleHandler ?? '');
774
+ this.triageProfile.set(settings?.triageProfile ?? '');
775
+ this.retryCount.set(String(settings?.retryCount ?? 3));
776
+ this.maxConcurrentRuns.set(String(settings?.maxConcurrentRuns ?? 4));
777
+ this.maxRunsPerSubjectPerDay.set(String(settings?.maxRunsPerSubjectPerDay ?? 10));
778
+ this.maxTokensPerRun.set(numberText(settings?.maxTokensPerRun));
779
+ this.dailyToolCalls.set(numberText(settings?.dailyToolCalls));
780
+ this.dailyTokens.set(numberText(settings?.dailyTokens));
781
+ this.allowGatewayReads.set(settings?.allowGatewayReads ?? false);
782
+ this.delegateFromInitiator.set(settings?.delegateFromInitiator ?? false);
783
+ this.elevatedReads.set(settings?.elevatedReads ?? 'restricted');
784
+ this.open('settings');
785
+ }
786
+ saveSettings() {
787
+ const request = {
788
+ accountability: this.accountability(),
789
+ serviceAccountActorId: this.accountability() === 'service-account' ? blank(this.serviceAccount()) : null,
790
+ delegateFromInitiator: this.delegateFromInitiator(),
791
+ allowGatewayReads: this.allowGatewayReads(),
792
+ elevatedReads: this.elevatedReads(),
793
+ approvalPolicy: blank(this.approvalPolicy()),
794
+ retryCount: toNumber(this.retryCount()),
795
+ maxConcurrentRuns: toNumber(this.maxConcurrentRuns()),
796
+ maxRunsPerSubjectPerDay: toNumber(this.maxRunsPerSubjectPerDay()),
797
+ maxTokensPerRun: toNumber(this.maxTokensPerRun()),
798
+ dailyToolCalls: toNumber(this.dailyToolCalls()),
799
+ dailyTokens: toNumber(this.dailyTokens()),
800
+ triageProfile: blank(this.triageProfile()),
801
+ ruleHandler: blank(this.ruleHandler()),
802
+ expectedVersion: this.configuration()?.runSettings?.version ?? null,
803
+ reason: blank(this.reason())
804
+ };
805
+ this.run(this.admin.saveRunSettings(this.agent().id, request), 'nh-assistant.admin.run-config.settings.saved');
806
+ }
807
+ removeSettings() {
808
+ const version = this.configuration()?.runSettings?.version ?? null;
809
+ this.run(this.admin.removeRunSettings(this.agent().id, { expectedVersion: version }), 'nh-assistant.admin.run-config.settings.removed');
810
+ }
811
+ addSkill() {
812
+ this.editingSkill.set(null);
813
+ this.fillSkill(null);
814
+ this.open('skill');
815
+ }
816
+ editSkill(skill) {
817
+ this.editingSkill.set(skill);
818
+ this.fillSkill(skill);
819
+ this.open('skill');
820
+ }
821
+ toggleTool(selector, selected) {
822
+ this.skillTools.update(tools => selected
823
+ ? [...tools.filter(tool => tool !== selector), selector]
824
+ : tools.filter(tool => tool !== selector));
825
+ }
826
+ saveSkill() {
827
+ const editing = this.editingSkill();
828
+ const skillId = editing?.skillId ?? this.skillId().trim();
829
+ if (this.skillIdInvalid() || this.descriptionInvalid() || this.skillInstructions().length > maxInstructions) {
830
+ return;
831
+ }
832
+ const request = {
833
+ description: this.skillDescription().trim(),
834
+ instructions: blank(this.skillInstructions()),
835
+ tools: this.skillTools(),
836
+ profile: blank(this.skillProfile()),
837
+ ruleHandler: blank(this.skillRuleHandler()),
838
+ approvalPolicy: blank(this.skillApprovalPolicy()),
839
+ maxTokensPerRun: toNumber(this.skillMaxTokens()),
840
+ dailyToolCalls: toNumber(this.skillDailyToolCalls()),
841
+ dailyTokens: toNumber(this.skillDailyTokens()),
842
+ expectedVersion: editing?.version ?? null,
843
+ reason: blank(this.reason()),
844
+ ...(editing ? { apply: this.skillApply() } : {})
845
+ };
846
+ const saved = editing && this.skillApply() === 'shadow'
847
+ ? 'nh-assistant.admin.run-config.skills.saved-candidate'
848
+ : editing ? 'nh-assistant.admin.run-config.skills.saved' : 'nh-assistant.admin.run-config.skills.added';
849
+ this.run(this.admin.saveDataSkill(this.agent().id, skillId, request), saved);
850
+ }
851
+ removeSkill(skill) {
852
+ this.run(this.admin.removeDataSkill(this.agent().id, skill.skillId, { expectedVersion: skill.version }), 'nh-assistant.admin.run-config.skills.removed');
853
+ }
854
+ promote(skill) {
855
+ const candidate = skill.candidate;
856
+ if (candidate) {
857
+ this.run(this.admin.promoteSkillCandidate(this.agent().id, skill.skillId, { version: candidate.version, reason: blank(this.reason()) }), 'nh-assistant.admin.run-config.candidate.promoted');
858
+ }
859
+ }
860
+ discard(skill) {
861
+ const candidate = skill.candidate;
862
+ if (candidate) {
863
+ this.run(this.admin.discardSkillCandidate(this.agent().id, skill.skillId, { version: candidate.version, reason: blank(this.reason()) }), 'nh-assistant.admin.run-config.candidate.discarded');
864
+ }
865
+ }
866
+ /** Shows or hides the comparison of a skill's candidate with its live version. */
867
+ toggleComparison(skill) {
868
+ if (this.comparison()?.skillId === skill.skillId) {
869
+ this.comparison.set(null);
870
+ return;
871
+ }
872
+ this.versions.set(null);
873
+ this.comparison.set({ skillId: skill.skillId, result: null, failed: false });
874
+ this.detailSubscription?.unsubscribe();
875
+ this.detailSubscription = this.admin.compareSkillCandidate(this.agent().id, skill.skillId).subscribe({
876
+ next: result => this.comparison.set({ skillId: skill.skillId, result, failed: false }),
877
+ error: () => this.comparison.set({ skillId: skill.skillId, result: null, failed: true })
878
+ });
879
+ }
880
+ /** Shows or hides the version history of a skill. */
881
+ toggleVersions(skill) {
882
+ if (this.versions()?.skillId === skill.skillId) {
883
+ this.versions.set(null);
884
+ return;
885
+ }
886
+ this.comparison.set(null);
887
+ this.versions.set({ skillId: skill.skillId, items: null, failed: false });
888
+ this.detailSubscription?.unsubscribe();
889
+ this.detailSubscription = this.admin.getDataSkillVersions(this.agent().id, skill.skillId).subscribe({
890
+ next: items => this.versions.set({ skillId: skill.skillId, items, failed: false }),
891
+ error: () => this.versions.set({ skillId: skill.skillId, items: null, failed: true })
892
+ });
893
+ }
894
+ addTrigger() {
895
+ this.editingTrigger.set(null);
896
+ this.fillTrigger(null);
897
+ this.open('trigger');
898
+ }
899
+ editTrigger(trigger) {
900
+ this.editingTrigger.set(trigger);
901
+ this.fillTrigger(trigger);
902
+ this.open('trigger');
903
+ }
904
+ /** A schedule skips a tick while a run is busy by default; an event coalesces. */
905
+ setTriggerSource(source) {
906
+ this.triggerSource.set(source);
907
+ if (this.editingTrigger() === null) {
908
+ this.triggerConflict.set(source === 'schedule' ? 'ignore' : 'coalesce');
909
+ }
910
+ }
911
+ saveTrigger() {
912
+ const editing = this.editingTrigger();
913
+ if (this.triggerIdInvalid()) {
914
+ return;
915
+ }
916
+ const schedule = this.triggerSource() === 'schedule';
917
+ const request = {
918
+ source: this.triggerSource(),
919
+ skill: blank(this.triggerSkill()),
920
+ event: schedule ? null : blank(this.triggerEvent()),
921
+ cron: schedule ? blank(this.triggerCron()) : null,
922
+ timeZone: schedule ? blank(this.triggerTimeZone()) : null,
923
+ subjectSet: schedule ? blank(this.triggerSubjectSet()) : null,
924
+ message: schedule ? blank(this.triggerMessage()) : null,
925
+ conflict: this.triggerConflict(),
926
+ debounceSeconds: schedule ? null : toNumber(this.triggerDebounce()),
927
+ expectedVersion: editing?.version ?? null,
928
+ reason: blank(this.reason())
929
+ };
930
+ this.run(this.admin.saveDataTrigger(this.agent().id, editing?.triggerId ?? this.triggerId().trim(), request), editing ? 'nh-assistant.admin.run-config.triggers.saved' : 'nh-assistant.admin.run-config.triggers.added');
931
+ }
932
+ setTriggerShadow(trigger, shadow) {
933
+ this.run(this.admin.setDataTriggerShadow(this.agent().id, trigger.triggerId, { mode: shadow ? 'shadow' : 'live', reason: null }), shadow ? 'nh-assistant.admin.run-config.triggers.in-shadow' : 'nh-assistant.admin.run-config.triggers.live');
934
+ }
935
+ removeTrigger(trigger) {
936
+ this.run(this.admin.removeDataTrigger(this.agent().id, trigger.triggerId, { expectedVersion: trigger.version }), 'nh-assistant.admin.run-config.triggers.removed');
937
+ }
938
+ confirm(action, id) {
939
+ this.form.set(null);
940
+ this.error.set(null);
941
+ this.saved.set(null);
942
+ this.reason.set('');
943
+ this.confirming.set({ action, id });
944
+ }
945
+ isConfirming(action, id) {
946
+ const confirming = this.confirming();
947
+ return confirming?.action === action && confirming.id === id;
948
+ }
949
+ cancel() {
950
+ this.form.set(null);
951
+ this.confirming.set(null);
952
+ this.error.set(null);
953
+ }
954
+ value(event) {
955
+ return event.target.value;
956
+ }
957
+ checked(event) {
958
+ return event.target.checked;
959
+ }
960
+ fillSkill(skill) {
961
+ this.skillId.set(skill?.skillId ?? '');
962
+ this.skillDescription.set(skill?.description ?? '');
963
+ this.skillInstructions.set(skill?.instructions ?? '');
964
+ this.skillTools.set([...(skill?.tools ?? [])]);
965
+ this.skillProfile.set(skill?.profile ?? '');
966
+ this.skillRuleHandler.set(skill?.ruleHandler ?? '');
967
+ this.skillApprovalPolicy.set(skill?.approvalPolicy ?? '');
968
+ this.skillMaxTokens.set(numberText(skill?.maxTokensPerRun));
969
+ this.skillDailyToolCalls.set(numberText(skill?.dailyToolCalls));
970
+ this.skillDailyTokens.set(numberText(skill?.dailyTokens));
971
+ // A real change is evaluated in shadow first; a small correction goes live.
972
+ this.skillApply.set('shadow');
973
+ }
974
+ fillTrigger(trigger) {
975
+ this.triggerId.set(trigger?.triggerId ?? '');
976
+ this.triggerSource.set(trigger?.source ?? 'event');
977
+ this.triggerEvent.set(trigger?.event ?? '');
978
+ this.triggerCron.set(trigger?.cron ?? '');
979
+ this.triggerTimeZone.set(trigger?.timeZone ?? '');
980
+ this.triggerSubjectSet.set(trigger?.subjectSet ?? '');
981
+ this.triggerMessage.set(trigger?.message ?? '');
982
+ this.triggerSkill.set(trigger?.skillId ?? '');
983
+ this.triggerConflict.set(trigger?.conflict ?? 'coalesce');
984
+ this.triggerDebounce.set(String(trigger?.debounceSeconds ?? 120));
985
+ }
986
+ /** Opens one form at a time and moves the focus into it. */
987
+ open(form) {
988
+ this.confirming.set(null);
989
+ this.error.set(null);
990
+ this.saved.set(null);
991
+ this.reason.set('');
992
+ this.form.set(form);
993
+ afterNextRender(() => {
994
+ this.host.nativeElement.querySelector(`[data-form="${form}"] input:not([disabled]), [data-form="${form}"] select, [data-form="${form}"] textarea`)?.focus();
995
+ }, { injector: this.injector });
996
+ }
997
+ /** Runs a change and reloads the configuration, which every change may affect. */
998
+ run(request, savedKey) {
999
+ this.busy.set(true);
1000
+ this.error.set(null);
1001
+ this.saved.set(null);
1002
+ this.actionSubscription?.unsubscribe();
1003
+ this.actionSubscription = request.subscribe({
1004
+ error: (error) => {
1005
+ this.busy.set(false);
1006
+ this.error.set(_toNhAssistantError(error));
1007
+ },
1008
+ complete: () => {
1009
+ this.busy.set(false);
1010
+ this.form.set(null);
1011
+ this.confirming.set(null);
1012
+ this.comparison.set(null);
1013
+ this.versions.set(null);
1014
+ this.saved.set(savedKey);
1015
+ this.changed.emit();
1016
+ this.load();
1017
+ }
1018
+ });
1019
+ }
1020
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentRunConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1021
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAgentRunConfigurationComponent, isStandalone: true, selector: "nh-assistant-agent-run-configuration", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || busy()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.run-config.title' | translate: { name: agent().displayName } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.description' | translate }}</p>\n </div>\n </header>\n\n @if (saved(); as key) {\n <p class=\"message success\" role=\"status\">{{ key | translate }}</p>\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 @if (loading() && !configuration()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (configuration(); as config) {\n @if (problems().length > 0) {\n <div class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />\n <div class=\"stack\">\n <strong>{{ 'nh-assistant.admin.run-config.problems.title' | translate }}</strong>\n <ul>\n @for (problem of problems(); track problem.kind + problem.id) {\n <li>\n {{ 'nh-assistant.admin.run-config.problems.kind.' + problem.kind | translate: { id: problem.id } }}\n {{ 'nh-assistant.admin.run-config.problems.code.' + problem.code | translate }}\n </li>\n }\n </ul>\n </div>\n </div>\n }\n\n @if (config.source === 'admin') {\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-settings-title'\" data-run-settings>\n <header class=\"section-header\">\n <h4 [id]=\"id + '-settings-title'\">{{ 'nh-assistant.admin.run-config.settings.title' | translate }}</h4>\n </header>\n @if (config.runSettings; as settings) {\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}</span>\n @if (settings.serviceAccountActorId) {\n <span class=\"mono\">{{ settings.serviceAccountActorId }}</span>\n }\n @if (settings.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: settings.ruleHandler } }}</span>\n }\n @if (settings.approvalPolicy) {\n <span>{{ 'nh-assistant.admin.run-config.policy-value' | translate: { name: settings.approvalPolicy } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}</span>\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n }\n @if (form() !== 'settings') {\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon [name]=\"config.runSettings ? 'edit' : 'plus'\" />\n {{ (config.runSettings ? 'nh-assistant.admin.run-config.settings.edit' : 'nh-assistant.admin.run-config.settings.add') | translate }}\n </button>\n @if (config.runSettings) {\n @if (isConfirming('remove-settings', config.agentId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSettings()\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-settings', config.agentId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.run-config.settings.remove' | translate }}\n </button>\n }\n }\n </div>\n } @else {\n <form class=\"stack\" data-form=\"settings\" (submit)=\"$event.preventDefault(); saveSettings()\">\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-accountability'\">{{ 'nh-assistant.admin.run-config.settings.accountability-label' | translate }}</label>\n <select [id]=\"id + '-accountability'\" [attr.aria-describedby]=\"id + '-accountability-hint'\" (change)=\"accountability.set($any(value($event)))\">\n @for (option of accountabilities; track option) {\n <option [value]=\"option\" [selected]=\"accountability() === option\">{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-accountability-hint'\">{{ 'nh-assistant.admin.run-config.settings.accountability-hint' | translate }}</p>\n </div>\n @if (accountability() === 'service-account') {\n <div class=\"field\">\n <label [for]=\"id + '-service-account'\">{{ 'nh-assistant.admin.run-config.settings.service-account' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"256\"\n [id]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [attr.aria-invalid]=\"fieldError('serviceAccountActorId') ? true : null\"\n [attr.aria-describedby]=\"id + '-service-account-hint'\"\n (input)=\"serviceAccount.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n @if (fieldError('serviceAccountActorId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </div>\n }\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-rule-handler'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"ruleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"ruleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"ruleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"approvalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"approvalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"approvalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n @if (fieldError('approvalPolicy'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <select [id]=\"id + '-triage'\" [attr.aria-describedby]=\"id + '-triage-hint'\" (change)=\"triageProfile.set(value($event))\">\n <option value=\"\" [selected]=\"triageProfile() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"triageProfile() === profile\">{{ profile }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n </div>\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-retries'\">{{ 'nh-assistant.admin.run-config.settings.retries' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"10\" [id]=\"id + '-retries'\" [value]=\"retryCount()\" [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\" (input)=\"retryCount.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-concurrent'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"100\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\" [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\" (input)=\"maxConcurrentRuns.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-per-subject'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"1000\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\" [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\" (input)=\"maxRunsPerSubjectPerDay.set(value($event))\" />\n </div>\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\" (input)=\"maxTokensPerRun.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyToolCalls.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-elevated'\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</label>\n <select [id]=\"id + '-elevated'\" (change)=\"elevatedReads.set($any(value($event)))\">\n @for (mode of elevatedReadModes; track mode) {\n <option [value]=\"mode\" [selected]=\"elevatedReads() === mode\">{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</option>\n }\n </select>\n </div>\n </div>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" (change)=\"allowGatewayReads.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n @if (fieldError('allowGatewayReads'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </span>\n </label>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}</span>\n </label>\n <div class=\"field\">\n <label [for]=\"id + '-settings-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-settings-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n </form>\n }\n </section>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-skills-title'\" data-skills>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-skills-title'\">{{ 'nh-assistant.admin.agents.skills.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy() || !canAddRunData() || form() === 'skill'\" (click)=\"addSkill()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.skills.add' | translate }}\n </button>\n </header>\n @if (!canAddRunData()) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.needs-settings' | translate }}</p>\n }\n\n @if (form() === 'skill') {\n <form class=\"stack card\" data-form=\"skill\" (submit)=\"$event.preventDefault(); saveSkill()\">\n <h5>\n @if (editingSkill(); as editing) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: editing.skillId, version: editing.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n @if (!editingSkill()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-skill-id'\"\n [value]=\"skillId()\"\n [attr.aria-invalid]=\"skillId().length > 0 && skillIdInvalid() ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-id-hint'\"\n (input)=\"skillId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">\n <span [class.error-text]=\"skillId().length > 0 && skillIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n </p>\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-description'\">{{ 'nh-assistant.admin.run-config.skills.description-label' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-skill-description'\"\n [maxLength]=\"maxDescription\"\n [value]=\"skillDescription()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-description-hint'\"\n (input)=\"skillDescription.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n @if (fieldError('description'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n }\n <span class=\"counter\">{{ 'nh-assistant.admin.characters' | translate: { count: skillDescription().length, max: maxDescription } }}</span>\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea\n rows=\"6\"\n [id]=\"id + '-skill-instructions'\"\n [value]=\"skillInstructions()\"\n [attr.aria-invalid]=\"skillInstructions().length > maxInstructions ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-instructions-hint'\"\n (input)=\"skillInstructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"skillInstructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: skillInstructions().length, max: maxInstructions } }}</span>\n </p>\n </div>\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (selector of config.toolSelectors; track selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"skillTools().includes(selector)\" (change)=\"toggleTool(selector, checked($event))\" />\n <span class=\"mono\">{{ selector }}</span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (fieldError('tools'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </fieldset>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-skill-rules'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"skillRuleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"skillRuleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"skillRuleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <select [id]=\"id + '-skill-profile'\" [attr.aria-invalid]=\"fieldError('profile') ? true : null\" (change)=\"skillProfile.set(value($event))\">\n <option value=\"\" [selected]=\"skillProfile() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-profile' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"skillProfile() === profile\">{{ profile }}</option>\n }\n </select>\n @if (fieldError('profile'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-skill-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"skillApprovalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"skillApprovalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-policy' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"skillApprovalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-skill-tokens'\" [value]=\"skillMaxTokens()\" [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\" (input)=\"skillMaxTokens.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-skill-calls'\" [value]=\"skillDailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyToolCalls.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"skillDailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n @if (editingSkill()) {\n <fieldset class=\"subsection\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"skillApply() === 'shadow'\" (change)=\"skillApply.set('shadow')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"skillApply() === 'live'\" (change)=\"skillApply.set('live')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</span>\n </label>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || skillIdInvalid() || descriptionInvalid() || skillInstructions().length > maxInstructions\">\n {{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n </form>\n }\n\n @if (config.codeSkills.length === 0 && config.dataSkills.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.skills.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (skill of config.codeSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-code-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n </li>\n }\n @for (skill of config.dataSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: skill.version } }}</span>\n @if (!skill.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n @if (skill.candidate) {\n <span class=\"badge warning\" data-candidate-badge>{{ 'nh-assistant.admin.run-config.candidate.badge' | translate: { version: skill.candidate.version } }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n @if (skill.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler } }}</span>\n }\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: skill.tools.length } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.changed' | translate: { user: skill.changedBy, date: (skill.changedAt | date: 'medium') } }}</span>\n </div>\n @if (skill.candidate; as candidate) {\n <div class=\"subsection\" data-candidate>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.explain' | translate: { version: candidate.version, description: candidate.description } }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"comparison()?.skillId === skill.skillId\" (click)=\"toggleComparison(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.candidate.compare' | translate }}\n </button>\n @if (isConfirming('promote', skill.skillId)) {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy()\" (click)=\"promote(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-promote' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else if (isConfirming('discard', skill.skillId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"discard(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-discard' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('promote', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.promote' | translate }}</button>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('discard', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.discard' | translate }}</button>\n }\n </div>\n @if (comparison(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.result; as result) {\n <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: skill.skillId } }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.evaluation.columns.measure' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.live-column' | translate: { version: result.live.version } }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.candidate-column' | translate: { version: result.candidate.version } }}</th>\n </tr>\n </thead>\n <tbody>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.runs' | translate }}</th><td>{{ result.live.runs | number }}</td><td>{{ result.candidate.runs | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.completed' | translate }}</th><td>{{ result.live.completed | number }}</td><td>{{ result.candidate.completed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.failed' | translate }}</th><td>{{ result.live.failed | number }}</td><td>{{ result.candidate.failed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.proposals' | translate }}</th><td>{{ result.live.proposals | number }}</td><td>{{ result.candidate.proposals | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.tokens' | translate }}</th><td>{{ result.live.tokens | number }}</td><td>{{ result.candidate.tokens | number }}</td></tr>\n </tbody>\n </table>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.paired' | translate: { count: result.pairedEvents, date: (result.from | date: 'medium') } }}</p>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </div>\n }\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSkill(skill)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"versions()?.skillId === skill.skillId\" (click)=\"toggleVersions(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.skills.versions' | translate }}\n </button>\n @if (isConfirming('remove-skill', skill.skillId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSkill(skill)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-skill', skill.skillId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (versions(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.items; as items) {\n <ul class=\"history\" data-versions>\n @for (version of items; track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: version.version } }}</span>\n <span class=\"badge\" [class.success]=\"version.status === 'live'\" [class.warning]=\"version.status === 'candidate'\">{{ 'nh-assistant.admin.run-config.status.' + version.status | translate }}</span>\n <span>{{ version.description }}</span>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: version.createdBy, date: (version.createdAt | date: 'medium') } }}</span>\n @if (version.reason) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: version.reason } }}</span>\n }\n </li>\n }\n </ul>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </li>\n }\n </ul>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-triggers-title'\" data-triggers>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-triggers-title'\">{{ 'nh-assistant.admin.run-config.triggers.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy() || !canAddRunData() || form() === 'trigger'\" (click)=\"addTrigger()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.triggers.add' | translate }}\n </button>\n </header>\n\n @if (form() === 'trigger') {\n <form class=\"stack card\" data-form=\"trigger\" (submit)=\"$event.preventDefault(); saveTrigger()\">\n <h5>\n @if (editingTrigger(); as editing) {\n {{ 'nh-assistant.admin.run-config.triggers.edit-title' | translate: { trigger: editing.triggerId } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.triggers.new-title' | translate }}\n }\n </h5>\n @if (!editingTrigger()) {\n <div class=\"field\">\n <label [for]=\"id + '-trigger-id'\">{{ 'nh-assistant.admin.run-config.triggers.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-trigger-id'\"\n [value]=\"triggerId()\"\n [attr.aria-invalid]=\"(triggerId().length > 0 && triggerIdInvalid()) || fieldError('triggerId') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-id-hint'\"\n (input)=\"triggerId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-id-hint'\">\n @if (fieldError('triggerId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span [class.error-text]=\"triggerId().length > 0 && triggerIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n }\n </p>\n </div>\n }\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.triggers.source' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"event\" [checked]=\"triggerSource() === 'event'\" (change)=\"setTriggerSource('event')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-event' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"schedule\" [checked]=\"triggerSource() === 'schedule'\" (change)=\"setTriggerSource('schedule')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-schedule' | translate }}</span>\n </label>\n </fieldset>\n @if (triggerSource() === 'event') {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-event'\">{{ 'nh-assistant.admin.run-config.triggers.event' | translate }}</label>\n <select [id]=\"id + '-trigger-event'\" [attr.aria-invalid]=\"fieldError('event') ? true : null\" (change)=\"triggerEvent.set(value($event))\">\n <option value=\"\" [selected]=\"triggerEvent() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (event of catalog()?.triggerEvents ?? []; track event.name) {\n <option [value]=\"event.name\" [selected]=\"triggerEvent() === event.name\">{{ event.name }} \u00B7 {{ event.description }}</option>\n }\n </select>\n @if (fieldError('event'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-debounce'\">{{ 'nh-assistant.admin.run-config.triggers.debounce' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"3600\" [id]=\"id + '-trigger-debounce'\" [value]=\"triggerDebounce()\" [attr.aria-invalid]=\"fieldError('debounceSeconds') ? true : null\" (input)=\"triggerDebounce.set(value($event))\" />\n </div>\n </div>\n } @else {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-cron'\">{{ 'nh-assistant.admin.run-config.triggers.cron' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"128\"\n [id]=\"id + '-trigger-cron'\"\n [value]=\"triggerCron()\"\n [attr.aria-invalid]=\"fieldError('cron') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-cron-hint'\"\n (input)=\"triggerCron.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-cron-hint'\">\n @if (fieldError('cron'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.triggers.cron-hint' | translate }}</span>\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-zone'\">{{ 'nh-assistant.admin.run-config.triggers.time-zone' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-trigger-zone'\" [value]=\"triggerTimeZone()\" [attr.placeholder]=\"'UTC'\" (input)=\"triggerTimeZone.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-set'\">{{ 'nh-assistant.admin.run-config.triggers.subject-set' | translate }}</label>\n <select [id]=\"id + '-trigger-set'\" [attr.aria-invalid]=\"fieldError('subjectSet') ? true : null\" (change)=\"triggerSubjectSet.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSubjectSet() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (set of catalog()?.subjectSets ?? []; track set.name) {\n <option [value]=\"set.name\" [selected]=\"triggerSubjectSet() === set.name\">{{ set.name }} \u00B7 {{ set.description }}</option>\n }\n </select>\n @if (fieldError('subjectSet'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-message'\">{{ 'nh-assistant.admin.run-config.triggers.message' | translate }}</label>\n <textarea rows=\"2\" [id]=\"id + '-trigger-message'\" [maxLength]=\"maxMessage\" [value]=\"triggerMessage()\" [attr.aria-invalid]=\"fieldError('message') ? true : null\" (input)=\"triggerMessage.set(value($event))\"></textarea>\n </div>\n }\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-skill'\">{{ 'nh-assistant.admin.run-config.triggers.skill' | translate }}</label>\n <select [id]=\"id + '-trigger-skill'\" [attr.aria-invalid]=\"fieldError('skill') ? true : null\" (change)=\"triggerSkill.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSkill() === ''\">{{ 'nh-assistant.admin.run-config.triggers.agent-routes' | translate }}</option>\n @for (skill of skillOptions(); track skill) {\n <option [value]=\"skill\" [selected]=\"triggerSkill() === skill\">{{ skill }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-conflict'\">{{ 'nh-assistant.admin.run-config.triggers.conflict' | translate }}</label>\n <select [id]=\"id + '-trigger-conflict'\" (change)=\"triggerConflict.set($any(value($event)))\">\n @for (conflict of conflicts; track conflict) {\n <option [value]=\"conflict\" [selected]=\"triggerConflict() === conflict\">{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + conflict | translate }}</option>\n }\n </select>\n </div>\n </div>\n @if (!editingTrigger()) {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.triggers.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-trigger-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-trigger-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || triggerIdInvalid()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n </form>\n }\n\n @if (config.codeTriggers.length === 0 && config.dataTriggers.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.triggers.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (trigger of config.codeTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-code-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n </div>\n @if (trigger.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.skill-value' | translate: { skill: trigger.skillId } }}</p>\n }\n </li>\n }\n @for (trigger of config.dataTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-data-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n @if (trigger.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n @if (!trigger.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <div class=\"meta\">\n @if (trigger.source === 'event') {\n <span class=\"mono\">{{ trigger.event }}</span>\n } @else {\n <span class=\"mono\">{{ trigger.cron }} {{ trigger.timeZone ?? 'UTC' }}</span>\n <span class=\"mono\">{{ trigger.subjectSet }}</span>\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: trigger.skillId } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + trigger.conflict | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: trigger.version } }}</span>\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editTrigger(trigger)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.data-trigger-shadow]=\"trigger.shadow ? 'live' : 'shadow'\" [disabled]=\"busy()\" (click)=\"setTriggerShadow(trigger, !trigger.shadow)\">\n {{ (trigger.shadow ? 'nh-assistant.admin.agents.shadow.action.live' : 'nh-assistant.admin.agents.shadow.action.shadow') | translate }}\n </button>\n @if (isConfirming('remove-trigger', trigger.triggerId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeTrigger(trigger)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-trigger', trigger.triggerId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n </li>\n }\n </ul>\n }\n </section>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}: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}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}\n", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1022
+ }
1023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentRunConfigurationComponent, decorators: [{
1024
+ type: Component,
1025
+ args: [{ selector: 'nh-assistant-agent-run-configuration', standalone: true, imports: [DatePipe, DecimalPipe, TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || busy()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.run-config.title' | translate: { name: agent().displayName } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.description' | translate }}</p>\n </div>\n </header>\n\n @if (saved(); as key) {\n <p class=\"message success\" role=\"status\">{{ key | translate }}</p>\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 @if (loading() && !configuration()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (configuration(); as config) {\n @if (problems().length > 0) {\n <div class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />\n <div class=\"stack\">\n <strong>{{ 'nh-assistant.admin.run-config.problems.title' | translate }}</strong>\n <ul>\n @for (problem of problems(); track problem.kind + problem.id) {\n <li>\n {{ 'nh-assistant.admin.run-config.problems.kind.' + problem.kind | translate: { id: problem.id } }}\n {{ 'nh-assistant.admin.run-config.problems.code.' + problem.code | translate }}\n </li>\n }\n </ul>\n </div>\n </div>\n }\n\n @if (config.source === 'admin') {\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-settings-title'\" data-run-settings>\n <header class=\"section-header\">\n <h4 [id]=\"id + '-settings-title'\">{{ 'nh-assistant.admin.run-config.settings.title' | translate }}</h4>\n </header>\n @if (config.runSettings; as settings) {\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}</span>\n @if (settings.serviceAccountActorId) {\n <span class=\"mono\">{{ settings.serviceAccountActorId }}</span>\n }\n @if (settings.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: settings.ruleHandler } }}</span>\n }\n @if (settings.approvalPolicy) {\n <span>{{ 'nh-assistant.admin.run-config.policy-value' | translate: { name: settings.approvalPolicy } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}</span>\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n }\n @if (form() !== 'settings') {\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon [name]=\"config.runSettings ? 'edit' : 'plus'\" />\n {{ (config.runSettings ? 'nh-assistant.admin.run-config.settings.edit' : 'nh-assistant.admin.run-config.settings.add') | translate }}\n </button>\n @if (config.runSettings) {\n @if (isConfirming('remove-settings', config.agentId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSettings()\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-settings', config.agentId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.run-config.settings.remove' | translate }}\n </button>\n }\n }\n </div>\n } @else {\n <form class=\"stack\" data-form=\"settings\" (submit)=\"$event.preventDefault(); saveSettings()\">\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-accountability'\">{{ 'nh-assistant.admin.run-config.settings.accountability-label' | translate }}</label>\n <select [id]=\"id + '-accountability'\" [attr.aria-describedby]=\"id + '-accountability-hint'\" (change)=\"accountability.set($any(value($event)))\">\n @for (option of accountabilities; track option) {\n <option [value]=\"option\" [selected]=\"accountability() === option\">{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-accountability-hint'\">{{ 'nh-assistant.admin.run-config.settings.accountability-hint' | translate }}</p>\n </div>\n @if (accountability() === 'service-account') {\n <div class=\"field\">\n <label [for]=\"id + '-service-account'\">{{ 'nh-assistant.admin.run-config.settings.service-account' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"256\"\n [id]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [attr.aria-invalid]=\"fieldError('serviceAccountActorId') ? true : null\"\n [attr.aria-describedby]=\"id + '-service-account-hint'\"\n (input)=\"serviceAccount.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n @if (fieldError('serviceAccountActorId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </div>\n }\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-rule-handler'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"ruleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"ruleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"ruleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"approvalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"approvalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"approvalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n @if (fieldError('approvalPolicy'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <select [id]=\"id + '-triage'\" [attr.aria-describedby]=\"id + '-triage-hint'\" (change)=\"triageProfile.set(value($event))\">\n <option value=\"\" [selected]=\"triageProfile() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"triageProfile() === profile\">{{ profile }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n </div>\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-retries'\">{{ 'nh-assistant.admin.run-config.settings.retries' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"10\" [id]=\"id + '-retries'\" [value]=\"retryCount()\" [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\" (input)=\"retryCount.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-concurrent'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"100\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\" [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\" (input)=\"maxConcurrentRuns.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-per-subject'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"1000\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\" [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\" (input)=\"maxRunsPerSubjectPerDay.set(value($event))\" />\n </div>\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\" (input)=\"maxTokensPerRun.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyToolCalls.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-elevated'\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</label>\n <select [id]=\"id + '-elevated'\" (change)=\"elevatedReads.set($any(value($event)))\">\n @for (mode of elevatedReadModes; track mode) {\n <option [value]=\"mode\" [selected]=\"elevatedReads() === mode\">{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</option>\n }\n </select>\n </div>\n </div>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" (change)=\"allowGatewayReads.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n @if (fieldError('allowGatewayReads'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </span>\n </label>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}</span>\n </label>\n <div class=\"field\">\n <label [for]=\"id + '-settings-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-settings-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n </form>\n }\n </section>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-skills-title'\" data-skills>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-skills-title'\">{{ 'nh-assistant.admin.agents.skills.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy() || !canAddRunData() || form() === 'skill'\" (click)=\"addSkill()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.skills.add' | translate }}\n </button>\n </header>\n @if (!canAddRunData()) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.needs-settings' | translate }}</p>\n }\n\n @if (form() === 'skill') {\n <form class=\"stack card\" data-form=\"skill\" (submit)=\"$event.preventDefault(); saveSkill()\">\n <h5>\n @if (editingSkill(); as editing) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: editing.skillId, version: editing.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n @if (!editingSkill()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-skill-id'\"\n [value]=\"skillId()\"\n [attr.aria-invalid]=\"skillId().length > 0 && skillIdInvalid() ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-id-hint'\"\n (input)=\"skillId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">\n <span [class.error-text]=\"skillId().length > 0 && skillIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n </p>\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-description'\">{{ 'nh-assistant.admin.run-config.skills.description-label' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-skill-description'\"\n [maxLength]=\"maxDescription\"\n [value]=\"skillDescription()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-description-hint'\"\n (input)=\"skillDescription.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n @if (fieldError('description'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n }\n <span class=\"counter\">{{ 'nh-assistant.admin.characters' | translate: { count: skillDescription().length, max: maxDescription } }}</span>\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea\n rows=\"6\"\n [id]=\"id + '-skill-instructions'\"\n [value]=\"skillInstructions()\"\n [attr.aria-invalid]=\"skillInstructions().length > maxInstructions ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-instructions-hint'\"\n (input)=\"skillInstructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"skillInstructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: skillInstructions().length, max: maxInstructions } }}</span>\n </p>\n </div>\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (selector of config.toolSelectors; track selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"skillTools().includes(selector)\" (change)=\"toggleTool(selector, checked($event))\" />\n <span class=\"mono\">{{ selector }}</span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (fieldError('tools'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </fieldset>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-skill-rules'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"skillRuleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"skillRuleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"skillRuleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <select [id]=\"id + '-skill-profile'\" [attr.aria-invalid]=\"fieldError('profile') ? true : null\" (change)=\"skillProfile.set(value($event))\">\n <option value=\"\" [selected]=\"skillProfile() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-profile' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"skillProfile() === profile\">{{ profile }}</option>\n }\n </select>\n @if (fieldError('profile'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-skill-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"skillApprovalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"skillApprovalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-policy' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"skillApprovalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-skill-tokens'\" [value]=\"skillMaxTokens()\" [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\" (input)=\"skillMaxTokens.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-skill-calls'\" [value]=\"skillDailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyToolCalls.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"skillDailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n @if (editingSkill()) {\n <fieldset class=\"subsection\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"skillApply() === 'shadow'\" (change)=\"skillApply.set('shadow')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"skillApply() === 'live'\" (change)=\"skillApply.set('live')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</span>\n </label>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || skillIdInvalid() || descriptionInvalid() || skillInstructions().length > maxInstructions\">\n {{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n </form>\n }\n\n @if (config.codeSkills.length === 0 && config.dataSkills.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.skills.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (skill of config.codeSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-code-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n </li>\n }\n @for (skill of config.dataSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: skill.version } }}</span>\n @if (!skill.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n @if (skill.candidate) {\n <span class=\"badge warning\" data-candidate-badge>{{ 'nh-assistant.admin.run-config.candidate.badge' | translate: { version: skill.candidate.version } }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n @if (skill.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler } }}</span>\n }\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: skill.tools.length } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.changed' | translate: { user: skill.changedBy, date: (skill.changedAt | date: 'medium') } }}</span>\n </div>\n @if (skill.candidate; as candidate) {\n <div class=\"subsection\" data-candidate>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.explain' | translate: { version: candidate.version, description: candidate.description } }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"comparison()?.skillId === skill.skillId\" (click)=\"toggleComparison(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.candidate.compare' | translate }}\n </button>\n @if (isConfirming('promote', skill.skillId)) {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy()\" (click)=\"promote(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-promote' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else if (isConfirming('discard', skill.skillId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"discard(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-discard' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('promote', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.promote' | translate }}</button>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('discard', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.discard' | translate }}</button>\n }\n </div>\n @if (comparison(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.result; as result) {\n <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: skill.skillId } }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.evaluation.columns.measure' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.live-column' | translate: { version: result.live.version } }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.candidate-column' | translate: { version: result.candidate.version } }}</th>\n </tr>\n </thead>\n <tbody>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.runs' | translate }}</th><td>{{ result.live.runs | number }}</td><td>{{ result.candidate.runs | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.completed' | translate }}</th><td>{{ result.live.completed | number }}</td><td>{{ result.candidate.completed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.failed' | translate }}</th><td>{{ result.live.failed | number }}</td><td>{{ result.candidate.failed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.proposals' | translate }}</th><td>{{ result.live.proposals | number }}</td><td>{{ result.candidate.proposals | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.tokens' | translate }}</th><td>{{ result.live.tokens | number }}</td><td>{{ result.candidate.tokens | number }}</td></tr>\n </tbody>\n </table>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.paired' | translate: { count: result.pairedEvents, date: (result.from | date: 'medium') } }}</p>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </div>\n }\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSkill(skill)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"versions()?.skillId === skill.skillId\" (click)=\"toggleVersions(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.skills.versions' | translate }}\n </button>\n @if (isConfirming('remove-skill', skill.skillId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSkill(skill)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-skill', skill.skillId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (versions(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.items; as items) {\n <ul class=\"history\" data-versions>\n @for (version of items; track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: version.version } }}</span>\n <span class=\"badge\" [class.success]=\"version.status === 'live'\" [class.warning]=\"version.status === 'candidate'\">{{ 'nh-assistant.admin.run-config.status.' + version.status | translate }}</span>\n <span>{{ version.description }}</span>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: version.createdBy, date: (version.createdAt | date: 'medium') } }}</span>\n @if (version.reason) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: version.reason } }}</span>\n }\n </li>\n }\n </ul>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </li>\n }\n </ul>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-triggers-title'\" data-triggers>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-triggers-title'\">{{ 'nh-assistant.admin.run-config.triggers.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy() || !canAddRunData() || form() === 'trigger'\" (click)=\"addTrigger()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.triggers.add' | translate }}\n </button>\n </header>\n\n @if (form() === 'trigger') {\n <form class=\"stack card\" data-form=\"trigger\" (submit)=\"$event.preventDefault(); saveTrigger()\">\n <h5>\n @if (editingTrigger(); as editing) {\n {{ 'nh-assistant.admin.run-config.triggers.edit-title' | translate: { trigger: editing.triggerId } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.triggers.new-title' | translate }}\n }\n </h5>\n @if (!editingTrigger()) {\n <div class=\"field\">\n <label [for]=\"id + '-trigger-id'\">{{ 'nh-assistant.admin.run-config.triggers.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-trigger-id'\"\n [value]=\"triggerId()\"\n [attr.aria-invalid]=\"(triggerId().length > 0 && triggerIdInvalid()) || fieldError('triggerId') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-id-hint'\"\n (input)=\"triggerId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-id-hint'\">\n @if (fieldError('triggerId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span [class.error-text]=\"triggerId().length > 0 && triggerIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n }\n </p>\n </div>\n }\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.triggers.source' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"event\" [checked]=\"triggerSource() === 'event'\" (change)=\"setTriggerSource('event')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-event' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"schedule\" [checked]=\"triggerSource() === 'schedule'\" (change)=\"setTriggerSource('schedule')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-schedule' | translate }}</span>\n </label>\n </fieldset>\n @if (triggerSource() === 'event') {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-event'\">{{ 'nh-assistant.admin.run-config.triggers.event' | translate }}</label>\n <select [id]=\"id + '-trigger-event'\" [attr.aria-invalid]=\"fieldError('event') ? true : null\" (change)=\"triggerEvent.set(value($event))\">\n <option value=\"\" [selected]=\"triggerEvent() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (event of catalog()?.triggerEvents ?? []; track event.name) {\n <option [value]=\"event.name\" [selected]=\"triggerEvent() === event.name\">{{ event.name }} \u00B7 {{ event.description }}</option>\n }\n </select>\n @if (fieldError('event'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-debounce'\">{{ 'nh-assistant.admin.run-config.triggers.debounce' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"3600\" [id]=\"id + '-trigger-debounce'\" [value]=\"triggerDebounce()\" [attr.aria-invalid]=\"fieldError('debounceSeconds') ? true : null\" (input)=\"triggerDebounce.set(value($event))\" />\n </div>\n </div>\n } @else {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-cron'\">{{ 'nh-assistant.admin.run-config.triggers.cron' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"128\"\n [id]=\"id + '-trigger-cron'\"\n [value]=\"triggerCron()\"\n [attr.aria-invalid]=\"fieldError('cron') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-cron-hint'\"\n (input)=\"triggerCron.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-cron-hint'\">\n @if (fieldError('cron'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.triggers.cron-hint' | translate }}</span>\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-zone'\">{{ 'nh-assistant.admin.run-config.triggers.time-zone' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-trigger-zone'\" [value]=\"triggerTimeZone()\" [attr.placeholder]=\"'UTC'\" (input)=\"triggerTimeZone.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-set'\">{{ 'nh-assistant.admin.run-config.triggers.subject-set' | translate }}</label>\n <select [id]=\"id + '-trigger-set'\" [attr.aria-invalid]=\"fieldError('subjectSet') ? true : null\" (change)=\"triggerSubjectSet.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSubjectSet() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (set of catalog()?.subjectSets ?? []; track set.name) {\n <option [value]=\"set.name\" [selected]=\"triggerSubjectSet() === set.name\">{{ set.name }} \u00B7 {{ set.description }}</option>\n }\n </select>\n @if (fieldError('subjectSet'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-message'\">{{ 'nh-assistant.admin.run-config.triggers.message' | translate }}</label>\n <textarea rows=\"2\" [id]=\"id + '-trigger-message'\" [maxLength]=\"maxMessage\" [value]=\"triggerMessage()\" [attr.aria-invalid]=\"fieldError('message') ? true : null\" (input)=\"triggerMessage.set(value($event))\"></textarea>\n </div>\n }\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-skill'\">{{ 'nh-assistant.admin.run-config.triggers.skill' | translate }}</label>\n <select [id]=\"id + '-trigger-skill'\" [attr.aria-invalid]=\"fieldError('skill') ? true : null\" (change)=\"triggerSkill.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSkill() === ''\">{{ 'nh-assistant.admin.run-config.triggers.agent-routes' | translate }}</option>\n @for (skill of skillOptions(); track skill) {\n <option [value]=\"skill\" [selected]=\"triggerSkill() === skill\">{{ skill }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-conflict'\">{{ 'nh-assistant.admin.run-config.triggers.conflict' | translate }}</label>\n <select [id]=\"id + '-trigger-conflict'\" (change)=\"triggerConflict.set($any(value($event)))\">\n @for (conflict of conflicts; track conflict) {\n <option [value]=\"conflict\" [selected]=\"triggerConflict() === conflict\">{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + conflict | translate }}</option>\n }\n </select>\n </div>\n </div>\n @if (!editingTrigger()) {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.triggers.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-trigger-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-trigger-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || triggerIdInvalid()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n </form>\n }\n\n @if (config.codeTriggers.length === 0 && config.dataTriggers.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.triggers.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (trigger of config.codeTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-code-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n </div>\n @if (trigger.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.skill-value' | translate: { skill: trigger.skillId } }}</p>\n }\n </li>\n }\n @for (trigger of config.dataTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-data-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n @if (trigger.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n @if (!trigger.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <div class=\"meta\">\n @if (trigger.source === 'event') {\n <span class=\"mono\">{{ trigger.event }}</span>\n } @else {\n <span class=\"mono\">{{ trigger.cron }} {{ trigger.timeZone ?? 'UTC' }}</span>\n <span class=\"mono\">{{ trigger.subjectSet }}</span>\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: trigger.skillId } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + trigger.conflict | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: trigger.version } }}</span>\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editTrigger(trigger)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.data-trigger-shadow]=\"trigger.shadow ? 'live' : 'shadow'\" [disabled]=\"busy()\" (click)=\"setTriggerShadow(trigger, !trigger.shadow)\">\n {{ (trigger.shadow ? 'nh-assistant.admin.agents.shadow.action.live' : 'nh-assistant.admin.agents.shadow.action.shadow') | translate }}\n </button>\n @if (isConfirming('remove-trigger', trigger.triggerId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeTrigger(trigger)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-trigger', trigger.triggerId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n </li>\n }\n </ul>\n }\n </section>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}: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}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}\n", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n"] }]
1026
+ }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
1027
+ function blank(value) {
1028
+ const trimmed = value.trim();
1029
+ return trimmed.length > 0 ? trimmed : null;
1030
+ }
1031
+ function toNumber(value) {
1032
+ const trimmed = value.trim();
1033
+ if (trimmed.length === 0) {
1034
+ return null;
1035
+ }
1036
+ const parsed = Number(trimmed);
1037
+ return Number.isFinite(parsed) ? parsed : null;
1038
+ }
1039
+ function numberText(value) {
1040
+ return value === null || value === undefined ? '' : String(value);
1041
+ }
1042
+
633
1043
  let nextId$4 = 0;
634
1044
  /** The longest reason the server stores with a state change. */
635
1045
  const maxStateReason = 500;
636
1046
  /**
637
1047
  * Agent administration: code agents (override, reset) and agents created here (edit, delete),
638
- * and for every agent its run state with pause, stop and resume, each with an optional reason.
1048
+ * and for every agent its run state with pause, stop and resume, its shadow mode and that of each
1049
+ * skill, and the technical account of an agent that answers to one, each with an optional reason.
639
1050
  * Editing opens `nh-assistant-admin-agent-editor`; the feedback action opens
640
- * `nh-assistant-agent-feedback-report` for the agent, and the evaluation action of an agent with
641
- * shadow variants opens `nh-assistant-agent-evaluation`.
1051
+ * `nh-assistant-agent-feedback-report` for the agent, the evaluation action of an agent with
1052
+ * shadow variants opens `nh-assistant-agent-evaluation`, and the run configuration action opens
1053
+ * `nh-assistant-agent-run-configuration` for the skills, triggers and run settings defined as data.
642
1054
  */
643
1055
  class NhAssistantAdminAgentsComponent {
644
1056
  constructor() {
@@ -660,10 +1072,17 @@ class NhAssistantAdminAgentsComponent {
660
1072
  this.reporting = signal(null, ...(ngDevMode ? [{ debugName: "reporting" }] : []));
661
1073
  /** The agent whose comparison with its shadow variants is shown instead of the list. */
662
1074
  this.evaluating = signal(null, ...(ngDevMode ? [{ debugName: "evaluating" }] : []));
1075
+ /** The agent whose run configuration is shown instead of the list. */
1076
+ this.configuring = signal(null, ...(ngDevMode ? [{ debugName: "configuring" }] : []));
663
1077
  this.confirming = signal(null, ...(ngDevMode ? [{ debugName: "confirming" }] : []));
664
1078
  this.busyAgentId = signal(null, ...(ngDevMode ? [{ debugName: "busyAgentId" }] : []));
665
1079
  /** The state change whose reason is being entered, of the agent or one of its skills. */
666
1080
  this.changingState = signal(null, ...(ngDevMode ? [{ debugName: "changingState" }] : []));
1081
+ /** The shadow mode change whose reason is being entered, of the agent or one of its skills. */
1082
+ this.changingShadow = signal(null, ...(ngDevMode ? [{ debugName: "changingShadow" }] : []));
1083
+ /** The change of the technical account being entered: a new account, or the one in code. */
1084
+ this.changingAccount = signal(null, ...(ngDevMode ? [{ debugName: "changingAccount" }] : []));
1085
+ this.accountId = signal('', ...(ngDevMode ? [{ debugName: "accountId" }] : []));
667
1086
  this.stateReason = signal('', ...(ngDevMode ? [{ debugName: "stateReason" }] : []));
668
1087
  this.maxReason = maxStateReason;
669
1088
  this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
@@ -719,6 +1138,33 @@ class NhAssistantAdminAgentsComponent {
719
1138
  this.evaluating.set(agent);
720
1139
  afterNextRender(() => this.backButton()?.nativeElement.focus(), { injector: this.injector });
721
1140
  }
1141
+ /**
1142
+ * Whether the agent can run on its own and so has a run configuration: an agent in code with runs,
1143
+ * or any agent created here, which gets run settings as data.
1144
+ */
1145
+ hasRunConfiguration(agent) {
1146
+ return agent.source === 'admin' || (agent.shadow !== null && agent.shadow !== undefined);
1147
+ }
1148
+ /** Shows the agent's skills, triggers and run settings defined as data. */
1149
+ openRunConfiguration(agent) {
1150
+ this.error.set(null);
1151
+ this.confirming.set(null);
1152
+ this.configuring.set(agent);
1153
+ afterNextRender(() => this.backButton()?.nativeElement.focus(), { injector: this.injector });
1154
+ }
1155
+ /** Returns to the list, refreshed, and to the run configuration action of the agent. */
1156
+ closeRunConfiguration() {
1157
+ const agentId = this.configuring()?.id;
1158
+ this.configuring.set(null);
1159
+ this.refreshAgents();
1160
+ if (agentId !== undefined) {
1161
+ afterNextRender(() => {
1162
+ const action = Array.from(this.host.nativeElement.querySelectorAll('[data-run-config-agent]'))
1163
+ .find(item => item.dataset['runConfigAgent'] === agentId);
1164
+ action?.focus();
1165
+ }, { injector: this.injector });
1166
+ }
1167
+ }
722
1168
  /** Returns to the list and to the evaluation action of the agent whose comparison was open. */
723
1169
  closeEvaluation() {
724
1170
  const agentId = this.evaluating()?.id;
@@ -777,12 +1223,68 @@ class NhAssistantAdminAgentsComponent {
777
1223
  }
778
1224
  /** Opens the reason form of a state change of the agent or, with a skill, of that skill. */
779
1225
  askState(agent, state, skill) {
780
- this.confirming.set(null);
781
- this.error.set(null);
782
- this.stateReason.set('');
1226
+ this.closeForms();
783
1227
  this.changingState.set({ agentId: agent.id, skillId: skill?.skillId, state });
784
1228
  afterNextRender(() => this.reasonInput()?.nativeElement.focus(), { injector: this.injector });
785
1229
  }
1230
+ /**
1231
+ * The shadow modes an agent or skill can change to: the opposite of its own mode, and the mode
1232
+ * in code once an administrator overrode it. A shadow variant, and an agent without runs that
1233
+ * only chats, offer none.
1234
+ */
1235
+ shadowActions(shadow) {
1236
+ if (!shadow || shadow.locked) {
1237
+ return [];
1238
+ }
1239
+ const own = shadow.override ?? shadow.configured;
1240
+ const actions = [own ? 'live' : 'shadow'];
1241
+ return shadow.override === null ? actions : [...actions, 'code'];
1242
+ }
1243
+ /** Opens the reason form of a shadow mode change of the agent or, with a skill, of that skill. */
1244
+ askShadow(agent, mode, skill) {
1245
+ this.closeForms();
1246
+ this.changingShadow.set({ agentId: agent.id, skillId: skill?.skillId, mode });
1247
+ afterNextRender(() => this.reasonInput()?.nativeElement.focus(), { injector: this.injector });
1248
+ }
1249
+ isChangingShadow(agentId, mode, skillId) {
1250
+ const change = this.changingShadow();
1251
+ return change?.agentId === agentId && change.mode === mode && change.skillId === skillId;
1252
+ }
1253
+ applyShadow(agent) {
1254
+ const change = this.changingShadow();
1255
+ if (change?.agentId !== agent.id) {
1256
+ return;
1257
+ }
1258
+ const reason = this.stateReason().trim();
1259
+ const request = { mode: change.mode, reason: reason.length > 0 ? reason : null };
1260
+ if (change.skillId !== undefined) {
1261
+ this.run(agent.id, this.admin.setSkillShadow(agent.id, change.skillId, request), skill => this.replace(withSkill(agent, skill)));
1262
+ return;
1263
+ }
1264
+ this.run(agent.id, this.admin.setAgentShadow(agent.id, request), state => this.replace(withState(agent, state)));
1265
+ }
1266
+ /** Opens the form that changes the technical account, or returns to the one in code. */
1267
+ askAccount(agent, mode) {
1268
+ this.closeForms();
1269
+ this.changingAccount.set({ agentId: agent.id, mode });
1270
+ afterNextRender(() => this.reasonInput()?.nativeElement.focus(), { injector: this.injector });
1271
+ }
1272
+ isChangingAccount(agentId, mode) {
1273
+ const change = this.changingAccount();
1274
+ return change?.agentId === agentId && change.mode === mode;
1275
+ }
1276
+ applyAccount(agent) {
1277
+ const change = this.changingAccount();
1278
+ if (change?.agentId !== agent.id) {
1279
+ return;
1280
+ }
1281
+ const actorId = change.mode === 'set' ? this.accountId().trim() : null;
1282
+ if (actorId === '') {
1283
+ return;
1284
+ }
1285
+ const reason = this.stateReason().trim();
1286
+ this.run(agent.id, this.admin.setServiceAccount(agent.id, { actorId, reason: reason.length > 0 ? reason : null }), state => this.replace(withState(agent, state)));
1287
+ }
786
1288
  isChangingState(agentId, state, skillId) {
787
1289
  const change = this.changingState();
788
1290
  return change?.agentId === agentId && change.state === state && change.skillId === skillId;
@@ -805,6 +1307,8 @@ class NhAssistantAdminAgentsComponent {
805
1307
  }
806
1308
  confirm(agentId, action) {
807
1309
  this.changingState.set(null);
1310
+ this.changingShadow.set(null);
1311
+ this.changingAccount.set(null);
808
1312
  this.confirming.set({ agentId, action });
809
1313
  }
810
1314
  isConfirming(agentId, action) {
@@ -823,6 +1327,8 @@ class NhAssistantAdminAgentsComponent {
823
1327
  this.busyAgentId.set(agentId);
824
1328
  this.confirming.set(null);
825
1329
  this.changingState.set(null);
1330
+ this.changingShadow.set(null);
1331
+ this.changingAccount.set(null);
826
1332
  this.error.set(null);
827
1333
  this.actionSubscription?.unsubscribe();
828
1334
  this.actionSubscription = request.subscribe({
@@ -837,15 +1343,37 @@ class NhAssistantAdminAgentsComponent {
837
1343
  }
838
1344
  });
839
1345
  }
1346
+ /** Closes the open confirmation and change forms before another opens; one is open at a time. */
1347
+ closeForms() {
1348
+ this.confirming.set(null);
1349
+ this.error.set(null);
1350
+ this.stateReason.set('');
1351
+ this.accountId.set('');
1352
+ this.changingState.set(null);
1353
+ this.changingShadow.set(null);
1354
+ this.changingAccount.set(null);
1355
+ }
840
1356
  replace(agent) {
841
1357
  this.agents.update(agents => agents.map(item => item.id === agent.id ? agent : item));
842
1358
  }
1359
+ /**
1360
+ * Reloads the agents behind the visible list, for example their skills after the run
1361
+ * configuration changed; the list stays as it is when that fails.
1362
+ */
1363
+ refreshAgents() {
1364
+ this.subscription?.unsubscribe();
1365
+ this.subscription = this.admin.getAgents().subscribe({
1366
+ next: agents => this.agents.set(agents),
1367
+ error: () => undefined
1368
+ });
1369
+ this.refreshChat();
1370
+ }
843
1371
  /** Agent changes affect the agent picker of the panel. */
844
1372
  refreshChat() {
845
1373
  void this.store.reloadStatus();
846
1374
  }
847
1375
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminAgentsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
848
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAdminAgentsComponent, isStandalone: true, selector: "nh-assistant-admin-agents", viewQueries: [{ propertyName: "backButton", first: true, predicate: ["back"], descendants: true, isSignal: true }, { propertyName: "reasonInput", first: true, predicate: ["reasonInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\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 @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <strong>{{ agent.displayName }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ agent.description }}</p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.server-count' | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.state.action.' + action | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.skills.explain.' + change.state | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.explain.' + change.state | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.state.confirm.' + change.state | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\n }\n </section>\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:14px}: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}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAdminAgentEditorComponent, selector: "nh-assistant-admin-agent-editor", inputs: ["agent", "tools", "servers"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantAgentEvaluationComponent, selector: "nh-assistant-agent-evaluation", inputs: ["agentId", "variants"] }, { kind: "component", type: NhAssistantAgentFeedbackReportComponent, selector: "nh-assistant-agent-feedback-report", inputs: ["agentId", "itemsPerPage"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1376
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAdminAgentsComponent, isStandalone: true, selector: "nh-assistant-admin-agents", viewQueries: [{ propertyName: "backButton", first: true, predicate: ["back"], descendants: true, isSignal: true }, { propertyName: "reasonInput", first: true, predicate: ["reasonInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (configuring(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRunConfiguration()\">\n {{ 'nh-assistant.admin.run-config.back' | translate }}\n </button>\n </div>\n <nh-assistant-agent-run-configuration [agent]=\"agent\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\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 @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <strong>{{ agent.displayName }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n @if (agent.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ agent.description }}</p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n }\n @if (agent.shadow; as shadow) {\n @if (shadow.shadow) {\n <p class=\"muted\">\n {{ (shadow.locked ? 'nh-assistant.admin.agents.shadow.locked' : 'nh-assistant.admin.agents.shadow.hint') | translate }}\n @if (shadow.changedAt && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: shadow.changedBy, date: (shadow.changedAt | date: 'medium') } }}\n }\n @if (shadow.reason && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: shadow.reason } }}\n }\n </p>\n }\n }\n @if (agent.serviceAccount; as account) {\n <p class=\"muted\" data-service-account>\n <span class=\"mono\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: account.actorId } }}</span>\n @if (account.overridden) {\n {{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: account.configuredActorId } }}\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.server-count' | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @if (agent.runState !== 'configured-off') {\n @for (mode of shadowActions(agent.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode)\">\n {{ 'nh-assistant.admin.agents.shadow.action.' + mode | translate }}\n </button>\n }\n }\n @if (agent.serviceAccount; as account) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"set\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'set')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'set')\">\n {{ 'nh-assistant.admin.agents.service-account.action.set' | translate }}\n </button>\n @if (account.overridden) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"code\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'code')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'code')\">\n {{ 'nh-assistant.admin.agents.service-account.action.code' | translate }}\n </button>\n }\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-run-config-agent]=\"agent.id\" (click)=\"openRunConfiguration(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.run-config' | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n @if (skill.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.state.action.' + action | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @for (mode of shadowActions(skill.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.shadow.action.' + mode | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ 'nh-assistant.admin.agents.shadow.action.' + mode | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.skills.explain.' + change.state | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.explain.' + change.state | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.state.confirm.' + change.state | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingShadow(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-shadow-form (submit)=\"$event.preventDefault(); applyShadow(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.shadow.skill-explain.' + change.mode | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.shadow.explain.' + change.mode | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.shadow.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingShadow.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingAccount(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-account-form (submit)=\"$event.preventDefault(); applyAccount(agent)\">\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: agent.serviceAccount?.configuredActorId } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ 'nh-assistant.admin.agents.service-account.input-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n required\n [id]=\"id + '-' + agent.id + '-account'\"\n maxLength=\"256\"\n [value]=\"accountId()\"\n (input)=\"accountId.set(value($event))\" />\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null || (change.mode === 'set' && accountId().trim() === '')\">\n {{ 'nh-assistant.admin.agents.service-account.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingAccount.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\n }\n </section>\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:14px}: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}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAdminAgentEditorComponent, selector: "nh-assistant-admin-agent-editor", inputs: ["agent", "tools", "servers"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantAgentEvaluationComponent, selector: "nh-assistant-agent-evaluation", inputs: ["agentId", "variants"] }, { kind: "component", type: NhAssistantAgentFeedbackReportComponent, selector: "nh-assistant-agent-feedback-report", inputs: ["agentId", "itemsPerPage"] }, { kind: "component", type: NhAssistantAgentRunConfigurationComponent, selector: "nh-assistant-agent-run-configuration", inputs: ["agent"], outputs: ["changed"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
849
1377
  }
850
1378
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminAgentsComponent, decorators: [{
851
1379
  type: Component,
@@ -856,8 +1384,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
856
1384
  _NhAssistantIconComponent,
857
1385
  NhAssistantAdminAgentEditorComponent,
858
1386
  NhAssistantAgentEvaluationComponent,
859
- NhAssistantAgentFeedbackReportComponent
860
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\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 @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <strong>{{ agent.displayName }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ agent.description }}</p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.server-count' | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.state.action.' + action | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.skills.explain.' + change.state | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.explain.' + change.state | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.state.confirm.' + change.state | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\n }\n </section>\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:14px}: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}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}\n"] }]
1387
+ NhAssistantAgentFeedbackReportComponent,
1388
+ NhAssistantAgentRunConfigurationComponent
1389
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (configuring(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRunConfiguration()\">\n {{ 'nh-assistant.admin.run-config.back' | translate }}\n </button>\n </div>\n <nh-assistant-agent-run-configuration [agent]=\"agent\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\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 @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <strong>{{ agent.displayName }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n @if (agent.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ agent.description }}</p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n }\n @if (agent.shadow; as shadow) {\n @if (shadow.shadow) {\n <p class=\"muted\">\n {{ (shadow.locked ? 'nh-assistant.admin.agents.shadow.locked' : 'nh-assistant.admin.agents.shadow.hint') | translate }}\n @if (shadow.changedAt && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: shadow.changedBy, date: (shadow.changedAt | date: 'medium') } }}\n }\n @if (shadow.reason && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: shadow.reason } }}\n }\n </p>\n }\n }\n @if (agent.serviceAccount; as account) {\n <p class=\"muted\" data-service-account>\n <span class=\"mono\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: account.actorId } }}</span>\n @if (account.overridden) {\n {{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: account.configuredActorId } }}\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.server-count' | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @if (agent.runState !== 'configured-off') {\n @for (mode of shadowActions(agent.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode)\">\n {{ 'nh-assistant.admin.agents.shadow.action.' + mode | translate }}\n </button>\n }\n }\n @if (agent.serviceAccount; as account) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"set\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'set')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'set')\">\n {{ 'nh-assistant.admin.agents.service-account.action.set' | translate }}\n </button>\n @if (account.overridden) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"code\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'code')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'code')\">\n {{ 'nh-assistant.admin.agents.service-account.action.code' | translate }}\n </button>\n }\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-run-config-agent]=\"agent.id\" (click)=\"openRunConfiguration(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.run-config' | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n @if (skill.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.state.action.' + action | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @for (mode of shadowActions(skill.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.shadow.action.' + mode | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ 'nh-assistant.admin.agents.shadow.action.' + mode | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.skills.explain.' + change.state | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.explain.' + change.state | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.state.confirm.' + change.state | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingShadow(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-shadow-form (submit)=\"$event.preventDefault(); applyShadow(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.shadow.skill-explain.' + change.mode | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.shadow.explain.' + change.mode | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.shadow.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingShadow.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingAccount(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-account-form (submit)=\"$event.preventDefault(); applyAccount(agent)\">\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: agent.serviceAccount?.configuredActorId } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ 'nh-assistant.admin.agents.service-account.input-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n required\n [id]=\"id + '-' + agent.id + '-account'\"\n maxLength=\"256\"\n [value]=\"accountId()\"\n (input)=\"accountId.set(value($event))\" />\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null || (change.mode === 'set' && accountId().trim() === '')\">\n {{ 'nh-assistant.admin.agents.service-account.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingAccount.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\n }\n </section>\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:14px}: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}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}\n"] }]
861
1390
  }], ctorParameters: () => [], propDecorators: { backButton: [{ type: i0.ViewChild, args: ['back', { isSignal: true }] }], reasonInput: [{ type: i0.ViewChild, args: ['reasonInput', { isSignal: true }] }] } });
862
1391
  function withState(agent, state) {
863
1392
  return {
@@ -868,7 +1397,9 @@ function withState(agent, state) {
868
1397
  stateChangedBy: state.changedBy,
869
1398
  stateReason: state.reason,
870
1399
  waitingInputs: state.waitingInputs,
871
- skills: state.skills ?? agent.skills
1400
+ skills: state.skills ?? agent.skills,
1401
+ shadow: state.shadow ?? agent.shadow,
1402
+ serviceAccount: state.serviceAccount === undefined ? agent.serviceAccount : state.serviceAccount
872
1403
  };
873
1404
  }
874
1405
  function withSkill(agent, skill) {
@@ -1304,5 +1835,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
1304
1835
  * Generated bundle index. Do not edit.
1305
1836
  */
1306
1837
 
1307
- export { NH_ASSISTANT_MAX_INSTRUCTIONS, NhAssistantAdminAgentEditorComponent, NhAssistantAdminAgentsComponent, NhAssistantAdminComponent, NhAssistantAdminContextComponent, NhAssistantAdminMcpServerEditorComponent, NhAssistantAdminMcpServersComponent, NhAssistantAdminMcpToolsComponent, NhAssistantAgentEvaluationComponent, NhAssistantAgentFeedbackReportComponent, nhAssistantSelectorMatches };
1838
+ export { NH_ASSISTANT_MAX_INSTRUCTIONS, NhAssistantAdminAgentEditorComponent, NhAssistantAdminAgentsComponent, NhAssistantAdminComponent, NhAssistantAdminContextComponent, NhAssistantAdminMcpServerEditorComponent, NhAssistantAdminMcpServersComponent, NhAssistantAdminMcpToolsComponent, NhAssistantAgentEvaluationComponent, NhAssistantAgentFeedbackReportComponent, NhAssistantAgentRunConfigurationComponent, nhAssistantSelectorMatches };
1308
1839
  //# sourceMappingURL=newheap-platform-ai-chat-admin.mjs.map