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.
- package/package.json +1 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/src/app/api/chat/route.ts +889 -92
- package/template/src/app/api/contacts/search/route.ts +71 -0
- package/template/src/app/api/tool-approval/route.ts +30 -2
- package/template/src/app/globals.css +223 -1
- package/template/src/components/ChatView.tsx +247 -27
- package/template/src/components/Composer.tsx +11 -8
- package/template/src/components/MessageCard.tsx +549 -29
- package/template/src/components/SettingsModal.tsx +7 -0
- package/template/src/lib/data.ts +5 -0
- package/template/src/lib/tooling/approvals.ts +24 -9
- package/template/src/lib/tooling/tools/communication.ts +178 -31
- package/template/src/lib/types.ts +12 -2
|
@@ -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
|
+
}
|