@runtypelabs/persona 1.47.0 → 2.0.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.
Files changed (73) hide show
  1. package/README.md +140 -8
  2. package/dist/index.cjs +90 -39
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1093 -25
  5. package/dist/index.d.ts +1093 -25
  6. package/dist/index.global.js +111 -60
  7. package/dist/index.global.js.map +1 -1
  8. package/dist/index.js +90 -39
  9. package/dist/index.js.map +1 -1
  10. package/dist/install.global.js +1 -1
  11. package/dist/install.global.js.map +1 -1
  12. package/dist/widget.css +852 -505
  13. package/package.json +1 -1
  14. package/src/artifacts-session.test.ts +80 -0
  15. package/src/client.test.ts +20 -21
  16. package/src/client.ts +153 -4
  17. package/src/components/approval-bubble.ts +45 -42
  18. package/src/components/artifact-card.ts +91 -0
  19. package/src/components/artifact-pane.ts +501 -0
  20. package/src/components/composer-builder.ts +32 -27
  21. package/src/components/event-stream-view.ts +40 -40
  22. package/src/components/feedback.ts +36 -36
  23. package/src/components/forms.ts +11 -11
  24. package/src/components/header-builder.test.ts +32 -0
  25. package/src/components/header-builder.ts +55 -36
  26. package/src/components/header-layouts.ts +58 -125
  27. package/src/components/launcher.ts +36 -21
  28. package/src/components/message-bubble.ts +92 -65
  29. package/src/components/messages.ts +2 -2
  30. package/src/components/panel.ts +42 -11
  31. package/src/components/reasoning-bubble.ts +23 -23
  32. package/src/components/registry.ts +4 -0
  33. package/src/components/suggestions.ts +1 -1
  34. package/src/components/tool-bubble.ts +32 -32
  35. package/src/defaults.ts +30 -4
  36. package/src/index.ts +80 -2
  37. package/src/install.ts +22 -0
  38. package/src/plugins/types.ts +23 -0
  39. package/src/postprocessors.ts +2 -2
  40. package/src/runtime/host-layout.ts +174 -0
  41. package/src/runtime/init.test.ts +236 -0
  42. package/src/runtime/init.ts +114 -55
  43. package/src/session.ts +173 -7
  44. package/src/styles/tailwind.css +1 -1
  45. package/src/styles/widget.css +852 -505
  46. package/src/types/theme.ts +354 -0
  47. package/src/types.ts +348 -16
  48. package/src/ui.docked.test.ts +104 -0
  49. package/src/ui.ts +1093 -244
  50. package/src/utils/artifact-gate.test.ts +255 -0
  51. package/src/utils/artifact-gate.ts +142 -0
  52. package/src/utils/artifact-resize.test.ts +64 -0
  53. package/src/utils/artifact-resize.ts +67 -0
  54. package/src/utils/attachment-manager.ts +10 -10
  55. package/src/utils/code-generators.test.ts +52 -0
  56. package/src/utils/code-generators.ts +40 -36
  57. package/src/utils/dock.ts +17 -0
  58. package/src/utils/dom-context.test.ts +504 -0
  59. package/src/utils/dom-context.ts +896 -0
  60. package/src/utils/dom.ts +12 -1
  61. package/src/utils/message-fingerprint.test.ts +187 -0
  62. package/src/utils/message-fingerprint.ts +105 -0
  63. package/src/utils/migration.ts +179 -0
  64. package/src/utils/morph.ts +1 -1
  65. package/src/utils/plugins.ts +175 -0
  66. package/src/utils/positioning.ts +4 -4
  67. package/src/utils/theme.test.ts +125 -0
  68. package/src/utils/theme.ts +216 -60
  69. package/src/utils/tokens.ts +682 -0
  70. package/src/voice/audio-playback-manager.ts +187 -0
  71. package/src/voice/runtype-voice-provider.ts +305 -69
  72. package/src/voice/voice-activity-detector.ts +90 -0
  73. package/src/voice/voice.test.ts +6 -5
package/src/session.ts CHANGED
@@ -10,7 +10,9 @@ import {
10
10
  InjectMessageOptions,
11
11
  InjectAssistantMessageOptions,
12
12
  InjectUserMessageOptions,
13
- InjectSystemMessageOptions
13
+ InjectSystemMessageOptions,
14
+ PersonaArtifactRecord,
15
+ PersonaArtifactManualUpsert
14
16
  } from "./types";
15
17
  import {
16
18
  generateUserMessageId,
@@ -19,14 +21,12 @@ import {
19
21
  import { IMAGE_ONLY_MESSAGE_FALLBACK_TEXT } from "./utils/content";
20
22
  import type {
21
23
  VoiceProvider,
22
- VoiceResult,
23
24
  VoiceStatus,
24
25
  VoiceConfig,
25
26
  TextToSpeechConfig
26
27
  } from "./types";
27
28
  import {
28
29
  createVoiceProvider,
29
- createBestAvailableVoiceProvider,
30
30
  isVoiceSupported
31
31
  } from "./voice";
32
32
 
@@ -42,6 +42,10 @@ type SessionCallbacks = {
42
42
  onStreamingChanged: (streaming: boolean) => void;
43
43
  onError?: (error: Error) => void;
44
44
  onVoiceStatusChanged?: (status: VoiceStatus) => void;
45
+ onArtifactsState?: (state: {
46
+ artifacts: PersonaArtifactRecord[];
47
+ selectedId: string | null;
48
+ }) => void;
45
49
  };
46
50
 
47
51
  export class AgentWidgetSession {
@@ -58,6 +62,9 @@ export class AgentWidgetSession {
58
62
  // Agent execution state
59
63
  private agentExecution: AgentExecutionState | null = null;
60
64
 
65
+ private artifacts = new Map<string, PersonaArtifactRecord>();
66
+ private selectedArtifactId: string | null = null;
67
+
61
68
  // Voice support
62
69
  private voiceProvider: VoiceProvider | null = null;
63
70
  private voiceActive = false;
@@ -136,6 +143,38 @@ export class AgentWidgetSession {
136
143
  return this.voiceStatus;
137
144
  }
138
145
 
146
+ /**
147
+ * Get the voice interruption mode from the provider (none/cancel/barge-in)
148
+ */
149
+ public getVoiceInterruptionMode(): "none" | "cancel" | "barge-in" {
150
+ if (this.voiceProvider?.getInterruptionMode) {
151
+ return this.voiceProvider.getInterruptionMode();
152
+ }
153
+ return "none";
154
+ }
155
+
156
+ /**
157
+ * Stop voice playback / cancel in-flight request without starting recording.
158
+ * Returns to idle state.
159
+ */
160
+ public stopVoicePlayback(): void {
161
+ if (this.voiceProvider?.stopPlayback) {
162
+ this.voiceProvider.stopPlayback();
163
+ }
164
+ }
165
+
166
+ /** Returns true if the barge-in mic stream is alive (hot mic between turns) */
167
+ public isBargeInActive(): boolean {
168
+ return this.voiceProvider?.isBargeInActive?.() ?? false;
169
+ }
170
+
171
+ /** Tear down the barge-in mic pipeline — "hang up" the always-on mic */
172
+ public async deactivateBargeIn(): Promise<void> {
173
+ if (this.voiceProvider?.deactivateBargeIn) {
174
+ await this.voiceProvider.deactivateBargeIn();
175
+ }
176
+ }
177
+
139
178
  // Pending placeholder IDs for Runtype two-phase voice flow
140
179
  private pendingVoiceUserMessageId: string | null = null;
141
180
  private pendingVoiceAssistantMessageId: string | null = null;
@@ -227,9 +266,12 @@ export class AgentWidgetSession {
227
266
  this.injectAssistantMessage({ content: result.text.trim() });
228
267
  }
229
268
 
230
- // If Runtype provider returned audio (server-side TTS), mark the
231
- // assistant message as already spoken so browser TTS doesn't double-speak
232
- if (result.audio?.base64) {
269
+ // Mark assistant message as already spoken so browser TTS doesn't
270
+ // double-speak. This covers both paths:
271
+ // - Batch: audio.base64 is present in the voice_response
272
+ // - Streaming: audio arrives as binary PCM chunks (no base64 here)
273
+ // In either case, the Runtype provider handles TTS — browser TTS must skip.
274
+ {
233
275
  const spokenId = this.pendingVoiceAssistantMessageId
234
276
  ?? [...this.messages].reverse().find(m => m.role === 'assistant')?.id;
235
277
  if (spokenId) this.ttsSpokenMessageIds.add(spokenId);
@@ -939,11 +981,128 @@ export class AgentWidgetSession {
939
981
  this.abortController = null;
940
982
  this.messages = [];
941
983
  this.agentExecution = null;
984
+ this.clearArtifactState();
942
985
  this.setStreaming(false);
943
986
  this.setStatus("idle");
944
987
  this.callbacks.onMessagesChanged([...this.messages]);
945
988
  }
946
989
 
990
+ public getArtifacts(): PersonaArtifactRecord[] {
991
+ return [...this.artifacts.values()];
992
+ }
993
+
994
+ public getArtifactById(id: string): PersonaArtifactRecord | undefined {
995
+ return this.artifacts.get(id);
996
+ }
997
+
998
+ public getSelectedArtifactId(): string | null {
999
+ return this.selectedArtifactId;
1000
+ }
1001
+
1002
+ public selectArtifact(id: string | null): void {
1003
+ this.selectedArtifactId = id;
1004
+ this.emitArtifactsState();
1005
+ }
1006
+
1007
+ public clearArtifacts(): void {
1008
+ this.clearArtifactState();
1009
+ }
1010
+
1011
+ public upsertArtifact(manual: PersonaArtifactManualUpsert): PersonaArtifactRecord {
1012
+ const id =
1013
+ manual.id ||
1014
+ `art_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
1015
+ if (manual.artifactType === "markdown") {
1016
+ const rec: PersonaArtifactRecord = {
1017
+ id,
1018
+ artifactType: "markdown",
1019
+ title: manual.title,
1020
+ status: "complete",
1021
+ markdown: manual.content
1022
+ };
1023
+ this.artifacts.set(id, rec);
1024
+ this.selectedArtifactId = id;
1025
+ this.emitArtifactsState();
1026
+ return rec;
1027
+ }
1028
+ const rec: PersonaArtifactRecord = {
1029
+ id,
1030
+ artifactType: "component",
1031
+ title: manual.title,
1032
+ status: "complete",
1033
+ component: manual.component,
1034
+ props: manual.props ?? {}
1035
+ };
1036
+ this.artifacts.set(id, rec);
1037
+ this.selectedArtifactId = id;
1038
+ this.emitArtifactsState();
1039
+ return rec;
1040
+ }
1041
+
1042
+ private clearArtifactState(): void {
1043
+ if (this.artifacts.size === 0 && this.selectedArtifactId === null) return;
1044
+ this.artifacts.clear();
1045
+ this.selectedArtifactId = null;
1046
+ this.emitArtifactsState();
1047
+ }
1048
+
1049
+ private emitArtifactsState(): void {
1050
+ this.callbacks.onArtifactsState?.({
1051
+ artifacts: [...this.artifacts.values()],
1052
+ selectedId: this.selectedArtifactId
1053
+ });
1054
+ }
1055
+
1056
+ private applyArtifactStreamEvent(ev: AgentWidgetEvent): void {
1057
+ switch (ev.type) {
1058
+ case "artifact_start": {
1059
+ if (ev.artifactType === "markdown") {
1060
+ this.artifacts.set(ev.id, {
1061
+ id: ev.id,
1062
+ artifactType: "markdown",
1063
+ title: ev.title,
1064
+ status: "streaming",
1065
+ markdown: ""
1066
+ });
1067
+ } else {
1068
+ this.artifacts.set(ev.id, {
1069
+ id: ev.id,
1070
+ artifactType: "component",
1071
+ title: ev.title,
1072
+ status: "streaming",
1073
+ component: ev.component ?? "",
1074
+ props: {}
1075
+ });
1076
+ }
1077
+ this.selectedArtifactId = ev.id;
1078
+ break;
1079
+ }
1080
+ case "artifact_delta": {
1081
+ const row = this.artifacts.get(ev.id);
1082
+ if (row?.artifactType === "markdown") {
1083
+ row.markdown = (row.markdown ?? "") + ev.artDelta;
1084
+ }
1085
+ break;
1086
+ }
1087
+ case "artifact_update": {
1088
+ const row = this.artifacts.get(ev.id);
1089
+ if (row?.artifactType === "component") {
1090
+ row.props = { ...row.props, ...ev.props };
1091
+ if (ev.component) row.component = ev.component;
1092
+ }
1093
+ break;
1094
+ }
1095
+ case "artifact_complete": {
1096
+ const row = this.artifacts.get(ev.id);
1097
+ if (row) row.status = "complete";
1098
+ break;
1099
+ }
1100
+ default:
1101
+ return;
1102
+ }
1103
+ this.emitArtifactsState();
1104
+ }
1105
+
947
1106
  public hydrateMessages(messages: AgentWidgetMessage[]) {
948
1107
  this.abortController?.abort();
949
1108
  this.abortController = null;
@@ -972,7 +1131,7 @@ export class AgentWidgetSession {
972
1131
  agentName: event.message.agentMetadata.agentName ?? '',
973
1132
  status: 'running',
974
1133
  currentIteration: event.message.agentMetadata.iteration ?? 0,
975
- maxIterations: 0
1134
+ maxTurns: 0
976
1135
  };
977
1136
  } else if (event.message.agentMetadata.iteration !== undefined) {
978
1137
  this.agentExecution.currentIteration = event.message.agentMetadata.iteration;
@@ -998,6 +1157,13 @@ export class AgentWidgetSession {
998
1157
  this.agentExecution.status = 'error';
999
1158
  }
1000
1159
  this.callbacks.onError?.(event.error);
1160
+ } else if (
1161
+ event.type === "artifact_start" ||
1162
+ event.type === "artifact_delta" ||
1163
+ event.type === "artifact_update" ||
1164
+ event.type === "artifact_complete"
1165
+ ) {
1166
+ this.applyArtifactStreamEvent(event);
1001
1167
  }
1002
1168
  };
1003
1169
 
@@ -14,7 +14,7 @@
14
14
  }
15
15
 
16
16
  @layer components {
17
- .tvw-widget-shadow {
17
+ .persona-widget-shadow {
18
18
  box-shadow: 0 25px 50px -12px rgba(15, 23, 42, 0.35);
19
19
  }
20
20
  }