@runtypelabs/persona 3.21.3 → 3.22.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.
- package/README.md +67 -0
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-CWPIj66R.d.cts → types-BZVr1YOV.d.cts} +10 -0
- package/dist/animations/{types-CWPIj66R.d.ts → types-BZVr1YOV.d.ts} +10 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/index.cjs +50 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +474 -6
- package/dist/index.d.ts +474 -6
- package/dist/index.global.js +98 -88
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +48 -41
- package/dist/index.js.map +1 -1
- package/dist/smart-dom-reader.cjs +1875 -0
- package/dist/smart-dom-reader.d.cts +4521 -0
- package/dist/smart-dom-reader.d.ts +4521 -0
- package/dist/smart-dom-reader.js +1848 -0
- package/dist/theme-editor.cjs +2281 -84
- package/dist/theme-editor.d.cts +348 -1
- package/dist/theme-editor.d.ts +348 -1
- package/dist/theme-editor.js +2260 -78
- package/package.json +9 -2
- package/src/client.test.ts +165 -0
- package/src/client.ts +144 -23
- package/src/index.ts +26 -0
- package/src/session.test.ts +258 -0
- package/src/session.ts +886 -30
- package/src/session.webmcp.test.ts +815 -0
- package/src/smart-dom-reader.test.ts +135 -0
- package/src/smart-dom-reader.ts +135 -0
- package/src/theme-editor/color-utils.test.ts +59 -0
- package/src/theme-editor/color-utils.ts +38 -2
- package/src/theme-editor/index.ts +35 -0
- package/src/theme-editor/webmcp/coerce.test.ts +86 -0
- package/src/theme-editor/webmcp/coerce.ts +286 -0
- package/src/theme-editor/webmcp/index.ts +45 -0
- package/src/theme-editor/webmcp/summary.ts +324 -0
- package/src/theme-editor/webmcp/tools.test.ts +205 -0
- package/src/theme-editor/webmcp/tools.ts +795 -0
- package/src/theme-editor/webmcp/types.ts +87 -0
- package/src/types.ts +186 -0
- package/src/ui.composer-keyboard.test.ts +229 -0
- package/src/ui.ts +127 -5
- package/src/utils/composer-history.test.ts +128 -0
- package/src/utils/composer-history.ts +113 -0
- package/src/utils/message-fingerprint.test.ts +20 -0
- package/src/utils/message-fingerprint.ts +2 -0
- package/src/utils/smart-dom-adapter.test.ts +257 -0
- package/src/utils/smart-dom-adapter.ts +217 -0
- package/{LICENSE → src/vendor/smart-dom-reader/LICENSE} +2 -2
- package/src/vendor/smart-dom-reader/README.md +61 -0
- package/src/vendor/smart-dom-reader/index.d.ts +476 -0
- package/src/vendor/smart-dom-reader/index.js +1618 -0
- package/src/webmcp-bridge.test.ts +429 -0
- package/src/webmcp-bridge.ts +547 -0
package/src/session.test.ts
CHANGED
|
@@ -794,3 +794,261 @@ describe('AgentWidgetSession.resolveApproval', () => {
|
|
|
794
794
|
expect(errors.length).toBe(1);
|
|
795
795
|
});
|
|
796
796
|
});
|
|
797
|
+
|
|
798
|
+
describe('AgentWidgetSession - dispatch error fallback', () => {
|
|
799
|
+
const originalFetch = global.fetch;
|
|
800
|
+
|
|
801
|
+
afterEach(() => {
|
|
802
|
+
global.fetch = originalFetch;
|
|
803
|
+
vi.restoreAllMocks();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// Make fetch reject so client.dispatch throws and the session falls back.
|
|
807
|
+
const failFetchWith = (error: Error) => {
|
|
808
|
+
global.fetch = vi.fn().mockRejectedValue(error);
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
const lastAssistantMessage = (
|
|
812
|
+
session: AgentWidgetSession
|
|
813
|
+
): AgentWidgetMessage | undefined =>
|
|
814
|
+
[...session.getMessages()].reverse().find((m) => m.role === 'assistant');
|
|
815
|
+
|
|
816
|
+
it('shows the default message with the underlying error detail and fires onError', async () => {
|
|
817
|
+
failFetchWith(new Error('Failed to fetch'));
|
|
818
|
+
const errors: Error[] = [];
|
|
819
|
+
const session = new AgentWidgetSession(
|
|
820
|
+
{ apiUrl: 'http://example.invalid/chat' },
|
|
821
|
+
{
|
|
822
|
+
onMessagesChanged: () => {},
|
|
823
|
+
onStatusChanged: () => {},
|
|
824
|
+
onStreamingChanged: () => {},
|
|
825
|
+
onError: (e) => errors.push(e),
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
|
|
829
|
+
await session.sendMessage('Hello');
|
|
830
|
+
|
|
831
|
+
const assistant = lastAssistantMessage(session);
|
|
832
|
+
expect(assistant?.content).toContain("I couldn't reach the assistant");
|
|
833
|
+
expect(assistant?.content).toContain('_Details: Failed to fetch_');
|
|
834
|
+
expect(errors).toHaveLength(1);
|
|
835
|
+
expect(errors[0].message).toBe('Failed to fetch');
|
|
836
|
+
expect(session.isStreaming()).toBe(false);
|
|
837
|
+
expect(session.getStatus()).toBe('idle');
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('uses a static errorMessage override verbatim', async () => {
|
|
841
|
+
failFetchWith(new Error('boom'));
|
|
842
|
+
const session = new AgentWidgetSession(
|
|
843
|
+
{
|
|
844
|
+
apiUrl: 'http://example.invalid/chat',
|
|
845
|
+
errorMessage: 'We are having trouble connecting.',
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
onMessagesChanged: () => {},
|
|
849
|
+
onStatusChanged: () => {},
|
|
850
|
+
onStreamingChanged: () => {},
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
|
|
854
|
+
await session.sendMessage('Hello');
|
|
855
|
+
|
|
856
|
+
const assistant = lastAssistantMessage(session);
|
|
857
|
+
expect(assistant?.content).toBe('We are having trouble connecting.');
|
|
858
|
+
expect(assistant?.content).not.toContain('Details');
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it('passes the error to a function errorMessage override', async () => {
|
|
862
|
+
failFetchWith(new Error('Failed to fetch'));
|
|
863
|
+
const seen: Error[] = [];
|
|
864
|
+
const session = new AgentWidgetSession(
|
|
865
|
+
{
|
|
866
|
+
apiUrl: 'http://example.invalid/chat',
|
|
867
|
+
errorMessage: (error) => {
|
|
868
|
+
seen.push(error);
|
|
869
|
+
return error.message.includes('Failed to fetch')
|
|
870
|
+
? 'You appear to be offline.'
|
|
871
|
+
: 'Something went wrong.';
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
onMessagesChanged: () => {},
|
|
876
|
+
onStatusChanged: () => {},
|
|
877
|
+
onStreamingChanged: () => {},
|
|
878
|
+
}
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
await session.sendMessage('Hello');
|
|
882
|
+
|
|
883
|
+
expect(seen).toHaveLength(1);
|
|
884
|
+
expect(seen[0]).toBeInstanceOf(Error);
|
|
885
|
+
expect(lastAssistantMessage(session)?.content).toBe(
|
|
886
|
+
'You appear to be offline.'
|
|
887
|
+
);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
it('suppresses the fallback bubble when the override returns an empty string but still fires onError', async () => {
|
|
891
|
+
failFetchWith(new Error('boom'));
|
|
892
|
+
const errors: Error[] = [];
|
|
893
|
+
const session = new AgentWidgetSession(
|
|
894
|
+
{
|
|
895
|
+
apiUrl: 'http://example.invalid/chat',
|
|
896
|
+
errorMessage: () => '',
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
onMessagesChanged: () => {},
|
|
900
|
+
onStatusChanged: () => {},
|
|
901
|
+
onStreamingChanged: () => {},
|
|
902
|
+
onError: (e) => errors.push(e),
|
|
903
|
+
}
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
await session.sendMessage('Hello');
|
|
907
|
+
|
|
908
|
+
expect(lastAssistantMessage(session)).toBeUndefined();
|
|
909
|
+
expect(errors).toHaveLength(1);
|
|
910
|
+
expect(session.isStreaming()).toBe(false);
|
|
911
|
+
expect(session.getStatus()).toBe('idle');
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it('does NOT show a fallback bubble when the dispatch is aborted', async () => {
|
|
915
|
+
const abortErr = new Error('The operation was aborted');
|
|
916
|
+
abortErr.name = 'AbortError';
|
|
917
|
+
failFetchWith(abortErr);
|
|
918
|
+
const errors: Error[] = [];
|
|
919
|
+
const session = new AgentWidgetSession(
|
|
920
|
+
{ apiUrl: 'http://example.invalid/chat' },
|
|
921
|
+
{
|
|
922
|
+
onMessagesChanged: () => {},
|
|
923
|
+
onStatusChanged: () => {},
|
|
924
|
+
onStreamingChanged: () => {},
|
|
925
|
+
onError: (e) => errors.push(e),
|
|
926
|
+
}
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
await session.sendMessage('Hello');
|
|
930
|
+
|
|
931
|
+
expect(lastAssistantMessage(session)).toBeUndefined();
|
|
932
|
+
expect(errors).toHaveLength(0);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
it('applies the override on continueConversation failures too', async () => {
|
|
936
|
+
failFetchWith(new Error('boom'));
|
|
937
|
+
const session = new AgentWidgetSession(
|
|
938
|
+
{
|
|
939
|
+
apiUrl: 'http://example.invalid/chat',
|
|
940
|
+
errorMessage: 'Custom continue error.',
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
onMessagesChanged: () => {},
|
|
944
|
+
onStatusChanged: () => {},
|
|
945
|
+
onStreamingChanged: () => {},
|
|
946
|
+
}
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
await session.continueConversation();
|
|
950
|
+
|
|
951
|
+
expect(lastAssistantMessage(session)?.content).toBe('Custom continue error.');
|
|
952
|
+
});
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
describe('AgentWidgetSession - WebMCP native approval gate', () => {
|
|
956
|
+
const makeSession = (
|
|
957
|
+
webmcp?: Record<string, unknown>
|
|
958
|
+
): { session: AgentWidgetSession; getMessages: () => AgentWidgetMessage[] } => {
|
|
959
|
+
let messages: AgentWidgetMessage[] = [];
|
|
960
|
+
const session = new AgentWidgetSession(
|
|
961
|
+
{ apiUrl: 'http://localhost:8000', webmcp: { enabled: true, ...webmcp } },
|
|
962
|
+
{
|
|
963
|
+
onMessagesChanged: (msgs) => { messages = msgs; },
|
|
964
|
+
onStatusChanged: () => {},
|
|
965
|
+
onStreamingChanged: () => {},
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
return { session, getMessages: () => messages };
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
it('renders a pending approval bubble and resolves true on approve', async () => {
|
|
972
|
+
const { session, getMessages } = makeSession();
|
|
973
|
+
const promise = session.requestWebMcpApproval({
|
|
974
|
+
toolName: 'add_to_cart',
|
|
975
|
+
args: { sku: 'SHOE-001' },
|
|
976
|
+
reason: 'gate',
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
const bubble = getMessages().find((m) => m.variant === 'approval');
|
|
980
|
+
expect(bubble).toBeDefined();
|
|
981
|
+
expect(bubble?.approval?.status).toBe('pending');
|
|
982
|
+
expect(bubble?.approval?.toolType).toBe('webmcp');
|
|
983
|
+
expect(bubble?.approval?.toolName).toBe('add_to_cart');
|
|
984
|
+
expect(bubble?.approval?.parameters).toEqual({ sku: 'SHOE-001' });
|
|
985
|
+
|
|
986
|
+
session.resolveWebMcpApproval(bubble!.id, 'approved');
|
|
987
|
+
await expect(promise).resolves.toBe(true);
|
|
988
|
+
|
|
989
|
+
const resolved = getMessages().find((m) => m.variant === 'approval');
|
|
990
|
+
expect(resolved?.approval?.status).toBe('approved');
|
|
991
|
+
expect(resolved?.approval?.resolvedAt).toBeDefined();
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('resolves false on deny and marks the bubble denied', async () => {
|
|
995
|
+
const { session, getMessages } = makeSession();
|
|
996
|
+
const promise = session.requestWebMcpApproval({
|
|
997
|
+
toolName: 'add_to_cart',
|
|
998
|
+
args: { sku: 'SHOE-002' },
|
|
999
|
+
reason: 'gate',
|
|
1000
|
+
});
|
|
1001
|
+
const bubble = getMessages().find((m) => m.variant === 'approval');
|
|
1002
|
+
|
|
1003
|
+
session.resolveWebMcpApproval(bubble!.id, 'denied');
|
|
1004
|
+
await expect(promise).resolves.toBe(false);
|
|
1005
|
+
expect(
|
|
1006
|
+
getMessages().find((m) => m.variant === 'approval')?.approval?.status
|
|
1007
|
+
).toBe('denied');
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it('auto-approves (no bubble) when autoApprove returns true', async () => {
|
|
1011
|
+
const { session, getMessages } = makeSession({
|
|
1012
|
+
autoApprove: (info: { toolName: string }) => info.toolName !== 'add_to_cart',
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
await expect(
|
|
1016
|
+
session.requestWebMcpApproval({
|
|
1017
|
+
toolName: 'search_products',
|
|
1018
|
+
args: { query: 'shoes' },
|
|
1019
|
+
reason: 'gate',
|
|
1020
|
+
})
|
|
1021
|
+
).resolves.toBe(true);
|
|
1022
|
+
// No approval bubble for an auto-approved (read-only) call.
|
|
1023
|
+
expect(getMessages().some((m) => m.variant === 'approval')).toBe(false);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
it('still gates a mutating tool when autoApprove excludes it', () => {
|
|
1027
|
+
const { session, getMessages } = makeSession({
|
|
1028
|
+
autoApprove: (info: { toolName: string }) => info.toolName !== 'add_to_cart',
|
|
1029
|
+
});
|
|
1030
|
+
void session.requestWebMcpApproval({
|
|
1031
|
+
toolName: 'add_to_cart',
|
|
1032
|
+
args: { sku: 'SHOE-001' },
|
|
1033
|
+
reason: 'gate',
|
|
1034
|
+
});
|
|
1035
|
+
expect(getMessages().some((m) => m.variant === 'approval')).toBe(true);
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
it('treats a second resolve as a no-op', async () => {
|
|
1039
|
+
const { session, getMessages } = makeSession();
|
|
1040
|
+
const promise = session.requestWebMcpApproval({
|
|
1041
|
+
toolName: 'add_to_cart',
|
|
1042
|
+
args: {},
|
|
1043
|
+
reason: 'gate',
|
|
1044
|
+
});
|
|
1045
|
+
const bubble = getMessages().find((m) => m.variant === 'approval');
|
|
1046
|
+
session.resolveWebMcpApproval(bubble!.id, 'approved');
|
|
1047
|
+
// Second call must not throw or flip the resolved status.
|
|
1048
|
+
expect(() => session.resolveWebMcpApproval(bubble!.id, 'denied')).not.toThrow();
|
|
1049
|
+
await expect(promise).resolves.toBe(true);
|
|
1050
|
+
expect(
|
|
1051
|
+
getMessages().find((m) => m.variant === 'approval')?.approval?.status
|
|
1052
|
+
).toBe('approved');
|
|
1053
|
+
});
|
|
1054
|
+
});
|