iris-chatbot 5.2.0 → 5.3.1

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.
@@ -0,0 +1,71 @@
1
+ import {
2
+ autocompleteContacts,
3
+ type ContactAutocompleteMode,
4
+ } from "../../../../lib/tooling/tools/communication";
5
+
6
+ export const runtime = "nodejs";
7
+ export const dynamic = "force-dynamic";
8
+
9
+ function isContactsPermissionError(message: string): boolean {
10
+ const normalized = message.toLowerCase();
11
+ return (
12
+ normalized.includes("not authorized") ||
13
+ normalized.includes("not permitted") ||
14
+ normalized.includes("permission") ||
15
+ normalized.includes("operation not permitted")
16
+ );
17
+ }
18
+
19
+ export async function POST(request: Request) {
20
+ try {
21
+ const body = (await request.json()) as {
22
+ query?: unknown;
23
+ mode?: unknown;
24
+ };
25
+ const query = typeof body.query === "string" ? body.query.trim() : "";
26
+ const mode: ContactAutocompleteMode =
27
+ body.mode === "email" ? "email" : "message";
28
+
29
+ if (!query) {
30
+ return new Response(
31
+ JSON.stringify({ ok: true, suggestions: [] }),
32
+ { headers: { "Content-Type": "application/json" } },
33
+ );
34
+ }
35
+
36
+ const suggestions = await autocompleteContacts({
37
+ query,
38
+ mode,
39
+ signal: request.signal,
40
+ });
41
+
42
+ return new Response(
43
+ JSON.stringify({ ok: true, suggestions }),
44
+ { headers: { "Content-Type": "application/json" } },
45
+ );
46
+ } catch (error) {
47
+ const message = error instanceof Error ? error.message : "Contacts lookup failed.";
48
+ if (isContactsPermissionError(message)) {
49
+ return new Response(
50
+ JSON.stringify({
51
+ ok: false,
52
+ permissionRequired: true,
53
+ error:
54
+ "Contacts access is required for suggestions. macOS should prompt the app running this server (Terminal/VS Code/Cursor).",
55
+ }),
56
+ {
57
+ status: 403,
58
+ headers: { "Content-Type": "application/json" },
59
+ },
60
+ );
61
+ }
62
+ return new Response(
63
+ JSON.stringify({ ok: false, error: message }),
64
+ {
65
+ status: 500,
66
+ headers: { "Content-Type": "application/json" },
67
+ },
68
+ );
69
+ }
70
+ }
71
+
@@ -8,6 +8,9 @@ export async function POST(request: Request) {
8
8
  const body = (await request.json()) as {
9
9
  approvalId?: unknown;
10
10
  decision?: unknown;
11
+ args?: unknown;
12
+ source?: unknown;
13
+ reasonCode?: unknown;
11
14
  };
12
15
 
13
16
  if (typeof body.approvalId !== "string" || !body.approvalId.trim()) {
@@ -17,7 +20,7 @@ export async function POST(request: Request) {
17
20
  });
18
21
  }
19
22
 
20
- if (body.decision !== "approve" && body.decision !== "deny") {
23
+ if (body.decision !== "approve" && body.decision !== "deny" && body.decision !== "supersede") {
21
24
  return new Response(JSON.stringify({ ok: false, error: "Invalid decision." }), {
22
25
  status: 400,
23
26
  headers: { "Content-Type": "application/json" },
@@ -25,8 +28,33 @@ export async function POST(request: Request) {
25
28
  }
26
29
 
27
30
  const approvalId = body.approvalId.trim();
31
+ const args =
32
+ body.args && typeof body.args === "object" && !Array.isArray(body.args)
33
+ ? (body.args as Record<string, unknown>)
34
+ : undefined;
35
+ if (body.args !== undefined && !args) {
36
+ return new Response(JSON.stringify({ ok: false, error: "Invalid args payload." }), {
37
+ status: 400,
38
+ headers: { "Content-Type": "application/json" },
39
+ });
40
+ }
41
+
42
+ const source =
43
+ body.source === "user" || body.source === "system"
44
+ ? body.source
45
+ : "user";
46
+ const reasonCode =
47
+ body.reasonCode === "user_cancel" ||
48
+ body.reasonCode === "internal_replace" ||
49
+ body.reasonCode === "timeout" ||
50
+ body.reasonCode === "other"
51
+ ? body.reasonCode
52
+ : undefined;
28
53
 
29
- const resolved = resolveApprovalDecision(approvalId, body.decision);
54
+ const resolved = resolveApprovalDecision(approvalId, body.decision, args, {
55
+ source,
56
+ reasonCode,
57
+ });
30
58
  if (!resolved) {
31
59
  return new Response(JSON.stringify({ ok: false, error: "Unable to resolve approval." }), {
32
60
  status: 404,
@@ -616,6 +616,220 @@ button:focus-visible {
616
616
  color: #111111;
617
617
  }
618
618
 
619
+ .draft-approval-card {
620
+ border: 1px solid var(--border);
621
+ border-radius: 14px;
622
+ background: var(--panel-2);
623
+ padding: 10px;
624
+ }
625
+
626
+ .draft-approval-header {
627
+ display: flex;
628
+ align-items: center;
629
+ justify-content: space-between;
630
+ gap: 8px;
631
+ margin-bottom: 10px;
632
+ }
633
+
634
+ .draft-approval-title {
635
+ font-size: 13px;
636
+ font-weight: 600;
637
+ color: var(--text-secondary);
638
+ }
639
+
640
+ .draft-approval-actions {
641
+ display: flex;
642
+ align-items: center;
643
+ gap: 6px;
644
+ }
645
+
646
+ .draft-approval-icon-btn {
647
+ height: 28px;
648
+ width: 28px;
649
+ border-radius: 999px;
650
+ border: 1px solid var(--border);
651
+ background: var(--panel);
652
+ color: var(--text-secondary);
653
+ display: inline-flex;
654
+ align-items: center;
655
+ justify-content: center;
656
+ transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
657
+ }
658
+
659
+ .draft-approval-icon-btn:hover {
660
+ border-color: var(--border-strong);
661
+ color: var(--text-primary);
662
+ }
663
+
664
+ .draft-approval-icon-btn.primary {
665
+ background: #ffffff;
666
+ color: #101010;
667
+ border-color: transparent;
668
+ }
669
+
670
+ [data-theme="light"] .draft-approval-icon-btn.primary {
671
+ background: #161616;
672
+ color: #ffffff;
673
+ }
674
+
675
+ .draft-approval-icon-btn:disabled {
676
+ opacity: 0.55;
677
+ cursor: not-allowed;
678
+ }
679
+
680
+ .draft-approval-field {
681
+ display: flex;
682
+ flex-direction: column;
683
+ gap: 6px;
684
+ margin-bottom: 10px;
685
+ }
686
+
687
+ .draft-approval-label {
688
+ font-size: 11px;
689
+ text-transform: uppercase;
690
+ letter-spacing: 0.08em;
691
+ color: var(--text-muted);
692
+ }
693
+
694
+ .draft-recipient-input-wrap {
695
+ min-height: 38px;
696
+ border: 1px solid var(--border);
697
+ border-radius: 10px;
698
+ background: var(--panel);
699
+ padding: 5px 6px;
700
+ display: flex;
701
+ align-items: center;
702
+ flex-wrap: wrap;
703
+ gap: 5px;
704
+ }
705
+
706
+ .draft-recipient-field-stack {
707
+ position: relative;
708
+ }
709
+
710
+ .draft-recipient-chip {
711
+ max-width: 100%;
712
+ border-radius: 999px;
713
+ border: 1px solid var(--border);
714
+ background: var(--panel-2);
715
+ color: var(--text-secondary);
716
+ font-size: 11px;
717
+ padding: 4px 8px;
718
+ display: inline-flex;
719
+ align-items: center;
720
+ gap: 4px;
721
+ }
722
+
723
+ .draft-recipient-chip-remove {
724
+ border: none;
725
+ background: transparent;
726
+ color: var(--text-muted);
727
+ display: inline-flex;
728
+ align-items: center;
729
+ justify-content: center;
730
+ }
731
+
732
+ .draft-recipient-chip-remove:hover {
733
+ color: var(--text-primary);
734
+ }
735
+
736
+ .draft-recipient-input {
737
+ border: none;
738
+ background: transparent;
739
+ color: var(--text-primary);
740
+ font-size: 12px;
741
+ flex: 1;
742
+ min-width: 120px;
743
+ padding: 4px 6px;
744
+ }
745
+
746
+ .draft-recipient-input:focus {
747
+ outline: none;
748
+ }
749
+
750
+ .draft-contact-suggestions {
751
+ margin-top: 4px;
752
+ border: 1px solid var(--border);
753
+ border-radius: 10px;
754
+ background: var(--panel);
755
+ max-height: 180px;
756
+ overflow-y: auto;
757
+ }
758
+
759
+ .draft-contact-suggestion-item {
760
+ width: 100%;
761
+ border: none;
762
+ border-bottom: 1px solid var(--border);
763
+ background: transparent;
764
+ text-align: left;
765
+ padding: 8px 10px;
766
+ display: flex;
767
+ flex-direction: column;
768
+ gap: 2px;
769
+ }
770
+
771
+ .draft-contact-suggestion-item:last-child {
772
+ border-bottom: none;
773
+ }
774
+
775
+ .draft-contact-suggestion-item:hover {
776
+ background: var(--panel-2);
777
+ }
778
+
779
+ .draft-contact-suggestion-name {
780
+ font-size: 12px;
781
+ color: var(--text-primary);
782
+ }
783
+
784
+ .draft-contact-suggestion-detail {
785
+ font-size: 11px;
786
+ color: var(--text-muted);
787
+ }
788
+
789
+ .draft-contact-suggestion-hint {
790
+ padding: 8px 10px;
791
+ font-size: 11px;
792
+ color: var(--text-muted);
793
+ }
794
+
795
+ .draft-approval-text-input,
796
+ .draft-approval-textarea {
797
+ width: 100%;
798
+ border: 1px solid var(--border);
799
+ border-radius: 10px;
800
+ background: var(--panel);
801
+ color: var(--text-primary);
802
+ font-size: 13px;
803
+ line-height: 1.5;
804
+ padding: 8px 10px;
805
+ resize: vertical;
806
+ }
807
+
808
+ .draft-approval-text-input:focus,
809
+ .draft-approval-textarea:focus {
810
+ outline: none;
811
+ border-color: var(--border-strong);
812
+ }
813
+
814
+ .draft-approval-footer {
815
+ display: flex;
816
+ justify-content: flex-end;
817
+ }
818
+
819
+ .draft-approval-cancel {
820
+ border-radius: 999px;
821
+ border: 1px solid var(--border);
822
+ background: var(--panel);
823
+ color: var(--text-secondary);
824
+ font-size: 11px;
825
+ padding: 4px 10px;
826
+ }
827
+
828
+ .draft-approval-cancel:hover {
829
+ border-color: var(--border-strong);
830
+ color: var(--text-primary);
831
+ }
832
+
619
833
  .message-content hr {
620
834
  border: 0;
621
835
  border-top: 1px solid var(--border);
@@ -873,6 +1087,14 @@ button:focus-visible {
873
1087
  .message-card.user {
874
1088
  max-width: 92%;
875
1089
  }
1090
+
1091
+ .draft-approval-header {
1092
+ align-items: flex-start;
1093
+ }
1094
+
1095
+ .draft-recipient-input {
1096
+ min-width: 92px;
1097
+ }
876
1098
  }
877
1099
 
878
1100
  .model-menu {
@@ -1103,4 +1325,4 @@ button:focus-visible {
1103
1325
 
1104
1326
  [data-theme="light"] .quoted-context-dismiss:hover {
1105
1327
  background: rgba(0, 0, 0, 0.06);
1106
- }
1328
+ }