@tencent-rtc/trtc-agent-skills 0.1.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 (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/README.zh.md +173 -0
  4. package/bin/cli.js +434 -0
  5. package/knowledge-base/index.yaml +454 -0
  6. package/knowledge-base/platform-slice-template.md +233 -0
  7. package/knowledge-base/scenario-spec.md +350 -0
  8. package/knowledge-base/scenarios/conference/base/general-conference.md +365 -0
  9. package/knowledge-base/scenarios/conference/base/webinar-conference.md +130 -0
  10. package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +145 -0
  11. package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +113 -0
  12. package/knowledge-base/scenarios/live/entertainment-live-room.md +118 -0
  13. package/knowledge-base/slice-spec.md +546 -0
  14. package/knowledge-base/slices/conference/web/ai-tools.md +225 -0
  15. package/knowledge-base/slices/conference/web/beauty-effects.md +188 -0
  16. package/knowledge-base/slices/conference/web/device-control.md +338 -0
  17. package/knowledge-base/slices/conference/web/login-auth.md +261 -0
  18. package/knowledge-base/slices/conference/web/network-quality.md +190 -0
  19. package/knowledge-base/slices/conference/web/official-roomkit-api.md +298 -0
  20. package/knowledge-base/slices/conference/web/official-roomkit-login-ui.md +246 -0
  21. package/knowledge-base/slices/conference/web/participant-list.md +238 -0
  22. package/knowledge-base/slices/conference/web/participant-management.md +718 -0
  23. package/knowledge-base/slices/conference/web/prejoin-check.md +293 -0
  24. package/knowledge-base/slices/conference/web/room-call.md +213 -0
  25. package/knowledge-base/slices/conference/web/room-chat.md +426 -0
  26. package/knowledge-base/slices/conference/web/room-lifecycle.md +534 -0
  27. package/knowledge-base/slices/conference/web/room-schedule.md +281 -0
  28. package/knowledge-base/slices/conference/web/screen-share.md +211 -0
  29. package/knowledge-base/slices/conference/web/video-layout.md +675 -0
  30. package/knowledge-base/slices/conference/web/virtual-background.md +197 -0
  31. package/knowledge-base/slices/conference/web/webinar-interaction.md +206 -0
  32. package/knowledge-base/slices/live/anchor-lifecycle.md +122 -0
  33. package/knowledge-base/slices/live/anchor-preview.md +90 -0
  34. package/knowledge-base/slices/live/anchor-room-config.md +104 -0
  35. package/knowledge-base/slices/live/audience-list.md +86 -0
  36. package/knowledge-base/slices/live/audience-manage.md +92 -0
  37. package/knowledge-base/slices/live/audience-watch.md +85 -0
  38. package/knowledge-base/slices/live/audio.md +116 -0
  39. package/knowledge-base/slices/live/barrage.md +88 -0
  40. package/knowledge-base/slices/live/beauty.md +99 -0
  41. package/knowledge-base/slices/live/coguest-apply.md +105 -0
  42. package/knowledge-base/slices/live/device-control.md +91 -0
  43. package/knowledge-base/slices/live/error-codes.md +167 -0
  44. package/knowledge-base/slices/live/gift.md +84 -0
  45. package/knowledge-base/slices/live/ios/.gitkeep +0 -0
  46. package/knowledge-base/slices/live/ios/anchor-lifecycle.md +313 -0
  47. package/knowledge-base/slices/live/ios/anchor-preview.md +228 -0
  48. package/knowledge-base/slices/live/ios/anchor-room-config.md +257 -0
  49. package/knowledge-base/slices/live/ios/audience-list.md +353 -0
  50. package/knowledge-base/slices/live/ios/audience-manage.md +381 -0
  51. package/knowledge-base/slices/live/ios/audience-watch.md +286 -0
  52. package/knowledge-base/slices/live/ios/audio.md +373 -0
  53. package/knowledge-base/slices/live/ios/barrage.md +285 -0
  54. package/knowledge-base/slices/live/ios/beauty.md +323 -0
  55. package/knowledge-base/slices/live/ios/coguest-apply.md +506 -0
  56. package/knowledge-base/slices/live/ios/device-control.md +286 -0
  57. package/knowledge-base/slices/live/ios/error-codes.md +270 -0
  58. package/knowledge-base/slices/live/ios/gift.md +315 -0
  59. package/knowledge-base/slices/live/ios/live-list.md +269 -0
  60. package/knowledge-base/slices/live/ios/login-auth.md +247 -0
  61. package/knowledge-base/slices/live/live-list.md +82 -0
  62. package/knowledge-base/slices/live/login-auth.md +78 -0
  63. package/package.json +34 -0
  64. package/skills/trtc/SKILL.md +326 -0
  65. package/skills/trtc/room-builder/SKILL.md +138 -0
  66. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +108 -0
  67. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/backend-contract.zh-CN.md +162 -0
  68. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/integration.zh-CN.md +154 -0
  69. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/theme.zh-CN.md +78 -0
  70. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/index.html +12 -0
  71. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/package.json +28 -0
  72. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/postcss.config.js +5 -0
  73. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/App.vue +25 -0
  74. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/ConsultationManagePanel.vue +838 -0
  75. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LanguageSwitch.vue +102 -0
  76. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LoadingSpinner.vue +6 -0
  77. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalAlert.vue +34 -0
  78. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalBusinessPanel.vue +148 -0
  79. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalButton.vue +49 -0
  80. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalConfirmDialog.vue +68 -0
  81. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalDataPanel.vue +196 -0
  82. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalRecordPanel.vue +270 -0
  83. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/PrescriptionPanel.vue +363 -0
  84. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/basic-info-config.ts +29 -0
  85. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
  86. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
  87. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/runtime-config.ts +12 -0
  88. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/env.d.ts +32 -0
  89. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +123 -0
  90. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +230 -0
  91. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +135 -0
  92. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +113 -0
  93. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +132 -0
  94. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +50 -0
  95. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/types.ts +77 -0
  96. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationChat.ts +97 -0
  97. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationDevices.ts +48 -0
  98. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationParticipants.ts +121 -0
  99. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationPermissions.ts +25 -0
  100. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/utils.ts +70 -0
  101. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/en-US/index.ts +553 -0
  102. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/index.ts +25 -0
  103. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/medicalTranslate.ts +85 -0
  104. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/state.ts +49 -0
  105. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/zh-CN/index.ts +463 -0
  106. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/main.ts +12 -0
  107. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/appointments.ts +96 -0
  108. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/users.ts +79 -0
  109. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/router/index.ts +63 -0
  110. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/index.ts +25 -0
  111. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/appointmentService.ts +77 -0
  112. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/authService.ts +38 -0
  113. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/launchContext.ts +31 -0
  114. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/userService.ts +35 -0
  115. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/appointmentService.ts +43 -0
  116. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/authService.ts +33 -0
  117. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/userService.ts +43 -0
  118. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/types.ts +135 -0
  119. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/shared/icons.ts +53 -0
  120. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/index.css +106 -0
  121. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/tailwind.css +3 -0
  122. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/theme.css +209 -0
  123. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/auth.ts +50 -0
  124. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/format.ts +24 -0
  125. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/navigation.ts +12 -0
  126. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/session.ts +28 -0
  127. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorConsultationView.vue +777 -0
  128. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorDashboardView.vue +678 -0
  129. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/LoginView.vue +441 -0
  130. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationFinishedView.vue +185 -0
  131. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationView.vue +1003 -0
  132. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientSelectDoctorView.vue +317 -0
  133. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientWaitingView.vue +454 -0
  134. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.json +21 -0
  135. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.node.json +8 -0
  136. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/vite.config.ts +17 -0
  137. package/skills/trtc/room-builder/templates/scenarios/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md +6 -0
  138. package/skills/trtc/room-builder/tools/render_ai_instructions.py +226 -0
  139. package/skills/trtc-apply/SKILL.md +97 -0
  140. package/skills/trtc-apply/guardrails/apply_lib/__init__.py +0 -0
  141. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  142. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/rule_parser.cpython-313.pyc +0 -0
  143. package/skills/trtc-apply/guardrails/apply_lib/rule_parser.py +268 -0
  144. package/skills/trtc-docs/SKILL.md +207 -0
  145. package/skills/trtc-onboarding/SKILL.md +839 -0
  146. package/skills/trtc-onboarding/reference/path-a1-demo.md +103 -0
  147. package/skills/trtc-onboarding/reference/path-a2-integrate.md +693 -0
  148. package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +115 -0
  149. package/skills/trtc-onboarding/reference/path-c-expand.md +43 -0
  150. package/skills/trtc-onboarding/reference/reporting-protocol.md +174 -0
  151. package/skills/trtc-onboarding/reference/supported-matrix.md +100 -0
  152. package/skills/trtc-onboarding/reference/usersig-handling.md +140 -0
  153. package/skills/trtc-search/SKILL.md +221 -0
  154. package/skills/trtc-topic/SKILL.md +638 -0
  155. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_read.cpython-313.pyc +0 -0
  156. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_write.cpython-313.pyc +0 -0
  157. package/skills/trtc-topic/guardrails/__pycache__/stop_require_apply_evidence.cpython-313.pyc +0 -0
  158. package/skills/trtc-topic/guardrails/gate_slice_read.py +133 -0
  159. package/skills/trtc-topic/guardrails/gate_slice_write.py +169 -0
  160. package/skills/trtc-topic/guardrails/stop_require_apply_evidence.py +97 -0
  161. package/skills/trtc-topic/references/execution-units.yaml +58 -0
  162. package/skills/trtc-topic/runtime/README.md +50 -0
  163. package/skills/trtc-topic/runtime/RUNTIME.md +128 -0
  164. package/skills/trtc-topic/runtime/lib/__init__.py +0 -0
  165. package/skills/trtc-topic/runtime/lib/platforms.py +194 -0
  166. package/skills/trtc-topic/runtime/package-lock.json +1211 -0
  167. package/skills/trtc-topic/runtime/package.json +13 -0
  168. package/skills/trtc-topic/runtime/telemetry-bridge.mjs +339 -0
  169. package/skills/trtc-topic/runtime/telemetry_collector.py +293 -0
  170. package/skills/trtc-topic/scripts/STATE-MACHINE-GUIDE.md +186 -0
  171. package/skills/trtc-topic/scripts/__pycache__/apply.cpython-313.pyc +0 -0
  172. package/skills/trtc-topic/scripts/apply.py +581 -0
  173. package/skills/trtc-topic/scripts/finalize_session.py +113 -0
  174. package/skills/trtc-topic/scripts/init_slice_queue.py +96 -0
  175. package/skills/trtc-topic/scripts/lib/__pycache__/state_machine.cpython-313.pyc +0 -0
  176. package/skills/trtc-topic/scripts/lib/state_machine.py +328 -0
  177. package/skills/trtc-topic/scripts/next_slice.py +137 -0
  178. package/skills/trtc-topic/tests/README.md +70 -0
  179. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  180. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  181. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.2.pyc +0 -0
  182. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  183. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.2.pyc +0 -0
  184. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.3.pyc +0 -0
  185. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.2.pyc +0 -0
  186. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.3.pyc +0 -0
  187. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.2.pyc +0 -0
  188. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.3.pyc +0 -0
  189. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.2.pyc +0 -0
  190. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.3.pyc +0 -0
  191. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.2.pyc +0 -0
  192. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.3.pyc +0 -0
  193. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.2.pyc +0 -0
  194. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.3.pyc +0 -0
  195. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.2.pyc +0 -0
  196. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.3.pyc +0 -0
  197. package/skills/trtc-topic/tests/conftest.py +72 -0
  198. package/skills/trtc-topic/tests/test_apply_cli.py +480 -0
  199. package/skills/trtc-topic/tests/test_end_to_end.py +305 -0
  200. package/skills/trtc-topic/tests/test_finalize_session.py +51 -0
  201. package/skills/trtc-topic/tests/test_gates.py +316 -0
  202. package/skills/trtc-topic/tests/test_session_resolver.py +260 -0
  203. package/skills/trtc-topic/tests/test_state_machine.py +414 -0
  204. package/skills/trtc-topic/tests/test_stop_require_apply.py +99 -0
  205. package/skills/trtc-topic/tests/test_topic_skill_invariants.py +130 -0
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "trtc-topic-runtime",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Runtime telemetry bridge for TRTC topic skill (Web platform)",
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node telemetry-bridge.mjs"
9
+ },
10
+ "dependencies": {
11
+ "puppeteer": "^23.0.0"
12
+ }
13
+ }
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+ // skills/trtc-topic/runtime/telemetry-bridge.mjs
3
+ //
4
+ // Web telemetry bridge for runtime log collection.
5
+ //
6
+ // Two modes:
7
+ // Mode A (default, visible): Launches a visible Chrome window + Vite dev
8
+ // server. User interacts directly in the Puppeteer-managed browser.
9
+ // Mode B (--connect): Connects to user's existing Chrome via CDP. No new
10
+ // browser launched, no Vite started. Attaches to the page matching the URL.
11
+ //
12
+ // In both modes, every page console event is forwarded as raw text to stdout,
13
+ // which telemetry_collector.py pipes into .trtc-telemetry/runtime.log.
14
+
15
+ import { spawn } from "node:child_process";
16
+ import { connect as tcpConnect } from "node:net";
17
+ import process from "node:process";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Argv parsing
21
+ // ---------------------------------------------------------------------------
22
+ function parseArgv(argv) {
23
+ const out = {};
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (a === "--url") out.url = argv[++i];
27
+ else if (a === "--workspace") out.workspace = argv[++i];
28
+ else if (a === "--connect") out.connect = argv[++i];
29
+ }
30
+ const missing = ["url", "workspace"].filter((k) => !out[k]);
31
+ if (missing.length) {
32
+ process.stderr.write(
33
+ `telemetry-bridge: missing required args: ${missing.join(", ")}\n` +
34
+ `usage: node telemetry-bridge.mjs --url <u> --workspace <path> [--connect <endpoint>]\n` +
35
+ `\n` +
36
+ `Modes:\n` +
37
+ ` (default) Launch visible Chrome + Vite dev server\n` +
38
+ ` --connect <endpoint> Connect to existing Chrome via CDP\n` +
39
+ ` e.g. --connect http://localhost:9222\n`,
40
+ );
41
+ process.exit(2);
42
+ }
43
+ return out;
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // TCP port probe
48
+ // ---------------------------------------------------------------------------
49
+ function parsePort(url) {
50
+ const m = url.match(/:(\d+)(?:\/|$)/);
51
+ return m ? Number.parseInt(m[1], 10) : 80;
52
+ }
53
+
54
+ function probeTcp(host, port, timeoutMs) {
55
+ return new Promise((resolve) => {
56
+ const socket = tcpConnect({ host, port });
57
+ let done = false;
58
+ const finish = (ok) => {
59
+ if (done) return;
60
+ done = true;
61
+ socket.destroy();
62
+ resolve(ok);
63
+ };
64
+ socket.once("connect", () => finish(true));
65
+ socket.once("error", () => finish(false));
66
+ setTimeout(() => finish(false), timeoutMs);
67
+ });
68
+ }
69
+
70
+ async function findFreePort(host, start, span) {
71
+ for (let p = start; p < start + span; p++) {
72
+ const occupied = await probeTcp(host, p, 200);
73
+ if (!occupied) return p;
74
+ }
75
+ return null;
76
+ }
77
+
78
+ async function waitForPort(host, port, attempts, intervalMs) {
79
+ for (let i = 0; i < attempts; i++) {
80
+ if (await probeTcp(host, port, intervalMs)) return true;
81
+ await new Promise((r) => setTimeout(r, intervalMs));
82
+ }
83
+ return false;
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Raw log emitter — outputs console text as-is, like Chrome DevTools
88
+ // ---------------------------------------------------------------------------
89
+ function emitRaw(line) {
90
+ process.stdout.write(line + "\n");
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Attach console listeners to a page
95
+ // ---------------------------------------------------------------------------
96
+ function attachPageListeners(page) {
97
+ page.on("console", (msg) => {
98
+ let text;
99
+ try { text = msg.text(); } catch { text = String(msg); }
100
+ const location = msg.location();
101
+ const source = location && location.url
102
+ ? `${location.url.split("/").pop()}:${location.lineNumber ?? 0}`
103
+ : "";
104
+ const prefix = source ? `${source} ` : "";
105
+ emitRaw(`${prefix}${text}`);
106
+ });
107
+
108
+ page.on("pageerror", (err) => {
109
+ const message = err && err.message ? err.message : String(err);
110
+ emitRaw(`[pageerror] ${message}`);
111
+ });
112
+
113
+ page.on("requestfailed", (req) => {
114
+ const failure = req.failure();
115
+ emitRaw(`[requestfailed] ${req.url()} :: ${failure ? failure.errorText : "unknown"}`);
116
+ });
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // Mode A: Launch visible browser + Vite dev server
121
+ // ---------------------------------------------------------------------------
122
+ async function modeVisible(puppeteer, url, workspace) {
123
+ const host = "localhost";
124
+ const requestedPort = parsePort(url);
125
+
126
+ // Find free port
127
+ const chosenPort = await findFreePort(host, requestedPort, 20);
128
+ if (chosenPort === null) {
129
+ emitRaw(`telemetry-bridge: no free port in [${requestedPort}, ${requestedPort + 20})`);
130
+ process.exit(1);
131
+ }
132
+ if (chosenPort !== requestedPort) {
133
+ emitRaw(`telemetry-bridge: port ${requestedPort} occupied; using ${chosenPort}`);
134
+ }
135
+ const targetUrl = url.replace(`:${requestedPort}`, `:${chosenPort}`);
136
+
137
+ // Spawn Vite dev server
138
+ const vite = spawn("npm", ["run", "dev", "--", "--port", String(chosenPort), "--strictPort"], {
139
+ cwd: workspace,
140
+ stdio: ["ignore", "pipe", "pipe"],
141
+ });
142
+ let viteExited = false;
143
+ vite.on("exit", (code, signal) => {
144
+ viteExited = true;
145
+ emitRaw(`telemetry-bridge: vite exited code=${code} signal=${signal || ""}`);
146
+ });
147
+ vite.stdout.resume();
148
+ vite.stderr.on("data", (chunk) => {
149
+ const text = chunk.toString("utf8").trimEnd();
150
+ if (text) emitRaw(`[vite] ${text}`);
151
+ });
152
+
153
+ // Wait for dev server
154
+ const ready = await waitForPort(host, chosenPort, 60, 1000);
155
+ if (!ready || viteExited) {
156
+ emitRaw(`telemetry-bridge: vite not ready on port ${chosenPort}`);
157
+ try { vite.kill("SIGTERM"); } catch {}
158
+ process.exit(1);
159
+ }
160
+
161
+ // Launch VISIBLE browser (headless: false)
162
+ let browser;
163
+ try {
164
+ browser = await puppeteer.launch({
165
+ headless: false,
166
+ args: [
167
+ "--no-sandbox",
168
+ "--disable-dev-shm-usage",
169
+ "--use-fake-ui-for-media-stream",
170
+ "--use-fake-device-for-media-stream",
171
+ ],
172
+ });
173
+ } catch (e) {
174
+ emitRaw(`telemetry-bridge: chromium launch failed: ${e.message}`);
175
+ try { vite.kill("SIGTERM"); } catch {}
176
+ process.exit(1);
177
+ }
178
+
179
+ const page = await browser.newPage();
180
+
181
+ // Install error probes before page load
182
+ await page.evaluateOnNewDocument(() => {
183
+ const tag = (probe, payload) =>
184
+ console.log(JSON.stringify({ __probe: probe, ...payload }));
185
+ window.addEventListener("error", (e) => {
186
+ tag("page_error", { message: (e && e.message) || String(e), filename: e && e.filename, lineno: e && e.lineno });
187
+ });
188
+ window.addEventListener("unhandledrejection", (e) => {
189
+ const reason = e && e.reason;
190
+ tag("unhandled_rejection", { message: reason && reason.message ? reason.message : String(reason) });
191
+ });
192
+ });
193
+
194
+ // Attach console listeners
195
+ attachPageListeners(page);
196
+
197
+ // Navigate
198
+ try {
199
+ await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 30_000 });
200
+ } catch (e) {
201
+ emitRaw(`telemetry-bridge: page.goto failed url=${targetUrl} err=${e.message}`);
202
+ }
203
+
204
+ emitRaw(`telemetry-bridge: [visible mode] page loaded at ${targetUrl}, capturing events...`);
205
+
206
+ // Shutdown handler — ensure Chromium is fully closed before exit
207
+ let shuttingDown = false;
208
+ const shutdown = async () => {
209
+ if (shuttingDown) return;
210
+ shuttingDown = true;
211
+ try { await browser.close(); } catch {}
212
+ try { vite.kill("SIGTERM"); } catch {}
213
+ // Give Vite a moment to exit gracefully, then force-kill
214
+ await new Promise((r) => setTimeout(r, 500));
215
+ try { if (!viteExited) vite.kill("SIGKILL"); } catch {}
216
+ process.exit(0);
217
+ };
218
+ process.on("SIGTERM", shutdown);
219
+ process.on("SIGINT", shutdown);
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Mode B: Connect to existing Chrome via CDP
224
+ // ---------------------------------------------------------------------------
225
+ async function modeConnect(puppeteer, connectEndpoint, targetUrl) {
226
+ emitRaw(`telemetry-bridge: [connect mode] connecting to ${connectEndpoint}`);
227
+
228
+ // Connect to existing browser
229
+ let browser;
230
+ try {
231
+ if (connectEndpoint.startsWith("ws://") || connectEndpoint.startsWith("wss://")) {
232
+ browser = await puppeteer.connect({ browserWSEndpoint: connectEndpoint });
233
+ } else {
234
+ browser = await puppeteer.connect({ browserURL: connectEndpoint });
235
+ }
236
+ } catch (e) {
237
+ emitRaw(`telemetry-bridge: failed to connect to browser at ${connectEndpoint}: ${e.message}`);
238
+ emitRaw(`Hint: Start Chrome with --remote-debugging-port=9222`);
239
+ process.exit(1);
240
+ }
241
+
242
+ emitRaw(`telemetry-bridge: connected to browser`);
243
+
244
+ // Find the target page
245
+ const pages = await browser.pages();
246
+ const targetPort = parsePort(targetUrl);
247
+ let page = pages.find((p) => {
248
+ const u = p.url();
249
+ return u.includes(`localhost:${targetPort}`) || u.includes(`127.0.0.1:${targetPort}`);
250
+ });
251
+
252
+ if (!page) {
253
+ // Try matching by full URL
254
+ page = pages.find((p) => p.url().includes(targetUrl));
255
+ }
256
+
257
+ if (!page) {
258
+ emitRaw(`telemetry-bridge: no page found matching ${targetUrl}. Listing open pages:`);
259
+ for (const p of pages) {
260
+ emitRaw(` - ${p.url()}`);
261
+ }
262
+ if (pages.length > 0) {
263
+ emitRaw(`telemetry-bridge: attaching to first available page. Open ${targetUrl} in the browser to capture its events.`);
264
+ page = pages[0];
265
+ } else {
266
+ emitRaw(`telemetry-bridge: no pages found in browser`);
267
+ await browser.disconnect();
268
+ process.exit(1);
269
+ }
270
+ } else {
271
+ emitRaw(`telemetry-bridge: found target page: ${page.url()}`);
272
+ }
273
+
274
+ // Attach console listeners
275
+ attachPageListeners(page);
276
+
277
+ // Also listen for new pages (user might navigate or open new tabs)
278
+ browser.on("targetcreated", async (target) => {
279
+ if (target.type() === "page") {
280
+ try {
281
+ const newPage = await target.page();
282
+ if (newPage) {
283
+ const newUrl = newPage.url();
284
+ if (newUrl.includes(`localhost:${targetPort}`) || newUrl.includes(`127.0.0.1:${targetPort}`)) {
285
+ emitRaw(`telemetry-bridge: new matching page detected: ${newUrl}`);
286
+ attachPageListeners(newPage);
287
+ }
288
+ }
289
+ } catch {}
290
+ }
291
+ });
292
+
293
+ emitRaw(`telemetry-bridge: [connect mode] capturing events from ${page.url()}...`);
294
+
295
+ // Shutdown handler — disconnect only, don't close user's browser
296
+ let shuttingDown = false;
297
+ const shutdown = async () => {
298
+ if (shuttingDown) return;
299
+ shuttingDown = true;
300
+ emitRaw(`telemetry-bridge: disconnecting (browser stays open)`);
301
+ try { await browser.disconnect(); } catch {}
302
+ setTimeout(() => process.exit(0), 100).unref();
303
+ };
304
+ process.on("SIGTERM", shutdown);
305
+ process.on("SIGINT", shutdown);
306
+ }
307
+
308
+ // ---------------------------------------------------------------------------
309
+ // Main
310
+ // ---------------------------------------------------------------------------
311
+ async function main() {
312
+ const args = parseArgv(process.argv.slice(2));
313
+ const { url, workspace, connect: connectEndpoint } = args;
314
+
315
+ emitRaw(`telemetry-bridge: starting (mode=${connectEndpoint ? "connect" : "visible"})`);
316
+
317
+ // Import Puppeteer
318
+ let puppeteer;
319
+ try {
320
+ puppeteer = (await import("puppeteer")).default;
321
+ } catch (e) {
322
+ emitRaw(`telemetry-bridge: puppeteer import failed: ${e.message}. Run \`cd runtime && npm install\`.`);
323
+ process.exit(1);
324
+ }
325
+
326
+ if (connectEndpoint) {
327
+ await modeConnect(puppeteer, connectEndpoint, url);
328
+ } else {
329
+ await modeVisible(puppeteer, url, workspace);
330
+ }
331
+
332
+ // Keep alive — shutdown is driven by SIGTERM from telemetry_collector.py
333
+ setInterval(() => {}, 60_000).unref();
334
+ }
335
+
336
+ main().catch((e) => {
337
+ emitRaw(`telemetry-bridge: fatal ${e && e.stack ? e.stack : e}`);
338
+ process.exit(1);
339
+ });
@@ -0,0 +1,293 @@
1
+ """telemetry_collector.py — Start/stop runtime log collection.
2
+
3
+ Usage:
4
+ python3 telemetry_collector.py --mode start --platform web --workspace /path/to/project
5
+ python3 telemetry_collector.py --mode stop --workspace /path/to/project
6
+
7
+ Starts a platform-specific log stream process in the background, piping its
8
+ stdout to <workspace>/.trtc-telemetry/runtime.log. Stores the PID for later
9
+ stop.
10
+
11
+ On stop: kills processes → filters errors from runtime.log → writes
12
+ runtime_error.log + runtime_context.json for MCP upload.
13
+ """
14
+ import argparse
15
+ import json
16
+ import os
17
+ import re
18
+ import signal
19
+ import subprocess
20
+ import sys
21
+ import time
22
+ from pathlib import Path
23
+
24
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
25
+ from lib.platforms import discover_devices, log_stream_command
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Error filtering patterns
30
+ # ---------------------------------------------------------------------------
31
+
32
+ # Match: lines that are actual SDK errors
33
+ _ERROR_PATTERNS = [
34
+ re.compile(r"<ERROR>"),
35
+ re.compile(r"error_code:\s*[1-9]\d*"), # Non-zero error_code
36
+ ]
37
+
38
+ # Exclude: false positives (skip even if _ERROR_PATTERNS match)
39
+ _ERROR_EXCLUSIONS = [
40
+ re.compile(r"telemetry-bridge:"),
41
+ re.compile(r"favicon\.ico.*404"),
42
+ re.compile(r"AudioContext was not allowed"),
43
+ re.compile(r"\[↑t\d\] on error"), # Event listener registration
44
+ re.compile(r"error_code:\s*0"), # Success callback
45
+ re.compile(r"error_message:\s*success"), # Success callback
46
+ ]
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Context extraction patterns
50
+ # ---------------------------------------------------------------------------
51
+
52
+ _CONTEXT_EXTRACTORS = {
53
+ "trtc_web": re.compile(r"TRTC Web SDK Version:\s*([\d.\w-]+)"),
54
+ "trtc_cloud": re.compile(r"TRTCCloud Version:\s*([\d.]+)"),
55
+ "room_engine": re.compile(r"TUIRoomEngine Web SDK Version:\s*([\d.]+)"),
56
+ "chat_engine": re.compile(r"TUIChatEngine-Lite\.VERSION:([\d.]+)"),
57
+ "os": re.compile(r"<INFO> OS:\s*(.+)"),
58
+ "sdk_app_id": re.compile(r"sdkAppId=(\d+)"),
59
+ "user_id": re.compile(r"userId=(\w+)"),
60
+ }
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Helpers
65
+ # ---------------------------------------------------------------------------
66
+
67
+ def _telemetry_dir(workspace: Path) -> Path:
68
+ """Return the .trtc-telemetry directory inside the workspace."""
69
+ d = workspace / ".trtc-telemetry"
70
+ d.mkdir(parents=True, exist_ok=True)
71
+ return d
72
+
73
+
74
+ def _filter_errors(runtime_log: Path, error_log: Path) -> list[str]:
75
+ """Filter error lines from runtime.log, write to runtime_error.log."""
76
+ if not runtime_log.exists() or runtime_log.stat().st_size == 0:
77
+ return []
78
+
79
+ errors: list[str] = []
80
+ try:
81
+ with open(runtime_log, "r", encoding="utf-8", errors="replace") as f:
82
+ for line in f:
83
+ line_stripped = line.strip()
84
+ if not line_stripped:
85
+ continue
86
+ # Check exclusions first
87
+ if any(exc.search(line_stripped) for exc in _ERROR_EXCLUSIONS):
88
+ continue
89
+ # Check if line matches any error pattern
90
+ if any(pat.search(line_stripped) for pat in _ERROR_PATTERNS):
91
+ errors.append(line_stripped)
92
+ except OSError:
93
+ return []
94
+
95
+ # Write error log (overwrite)
96
+ if errors:
97
+ try:
98
+ error_log.write_text("\n".join(errors) + "\n", encoding="utf-8")
99
+ except OSError:
100
+ pass
101
+ else:
102
+ # Clear previous error log if no errors this run
103
+ try:
104
+ error_log.unlink(missing_ok=True)
105
+ except OSError:
106
+ pass
107
+
108
+ return errors
109
+
110
+
111
+ def _extract_context(runtime_log: Path, context_file: Path) -> None:
112
+ """Extract environment context from runtime.log, write to runtime_context.json."""
113
+ if not runtime_log.exists() or runtime_log.stat().st_size == 0:
114
+ return
115
+
116
+ sdk_versions: dict[str, str] = {}
117
+ context: dict[str, str] = {}
118
+
119
+ version_keys = {"trtc_web", "trtc_cloud", "room_engine", "chat_engine"}
120
+
121
+ try:
122
+ with open(runtime_log, "r", encoding="utf-8", errors="replace") as f:
123
+ for line in f:
124
+ for key, pattern in _CONTEXT_EXTRACTORS.items():
125
+ if key in sdk_versions or key in context:
126
+ continue # Already found
127
+ m = pattern.search(line)
128
+ if m:
129
+ value = m.group(1).strip()
130
+ if key in version_keys:
131
+ sdk_versions[key] = value
132
+ else:
133
+ context[key] = value
134
+ # Early exit if all found
135
+ if len(sdk_versions) + len(context) == len(_CONTEXT_EXTRACTORS):
136
+ break
137
+ except OSError:
138
+ return
139
+
140
+ result = {}
141
+ if sdk_versions:
142
+ result["sdk_versions"] = sdk_versions
143
+ if context.get("os"):
144
+ result["os"] = context["os"]
145
+ if context.get("sdk_app_id"):
146
+ result["sdk_app_id"] = context["sdk_app_id"]
147
+ if context.get("user_id"):
148
+ result["user_id"] = context["user_id"]
149
+
150
+ if result:
151
+ try:
152
+ context_file.write_text(
153
+ json.dumps(result, indent=2, ensure_ascii=False) + "\n",
154
+ encoding="utf-8",
155
+ )
156
+ except OSError:
157
+ pass
158
+
159
+
160
+ # ---------------------------------------------------------------------------
161
+ # Start / Stop
162
+ # ---------------------------------------------------------------------------
163
+
164
+ def _start(platform: str, workspace: Path, connect: str | None = None) -> int:
165
+ """Start log collection in the background."""
166
+ tel_dir = _telemetry_dir(workspace)
167
+ runtime_log = tel_dir / "runtime.log"
168
+ pid_file = tel_dir / "collector.pid"
169
+
170
+ # Check if already running
171
+ if pid_file.exists():
172
+ pid = int(pid_file.read_text().strip())
173
+ try:
174
+ os.kill(pid, 0) # Check if process exists
175
+ print(json.dumps({
176
+ "status": "already_running",
177
+ "pid": pid,
178
+ }))
179
+ return 0
180
+ except ProcessLookupError:
181
+ pid_file.unlink(missing_ok=True)
182
+
183
+ # Discover device
184
+ devices = discover_devices(platform)
185
+ if not devices:
186
+ print(json.dumps({
187
+ "status": "error",
188
+ "message": f"No {platform} device found.",
189
+ }))
190
+ return 1
191
+
192
+ device = devices[0]
193
+
194
+ # Get log stream command
195
+ try:
196
+ cmd = log_stream_command(platform, device, workspace=workspace, connect=connect)
197
+ except ValueError as e:
198
+ print(json.dumps({"status": "error", "message": str(e)}))
199
+ return 1
200
+
201
+ # Launch in background, stdout → runtime.log (truncate previous)
202
+ f = open(runtime_log, "wb")
203
+ try:
204
+ proc = subprocess.Popen(
205
+ cmd,
206
+ stdout=f,
207
+ stderr=subprocess.STDOUT,
208
+ start_new_session=True, # Create new process group for clean kill
209
+ )
210
+ except FileNotFoundError as e:
211
+ f.close()
212
+ print(json.dumps({"status": "error", "message": f"Command not found: {e}"}))
213
+ return 1
214
+
215
+ pid_file.write_text(str(proc.pid))
216
+
217
+ print(json.dumps({
218
+ "status": "started",
219
+ "pid": proc.pid,
220
+ "platform": platform,
221
+ "device": device.name or device.id,
222
+ }))
223
+ return 0
224
+
225
+
226
+ def _stop(workspace: Path) -> int:
227
+ """Stop log collection, filter errors, extract context."""
228
+ tel_dir = _telemetry_dir(workspace)
229
+ pid_file = tel_dir / "collector.pid"
230
+
231
+ if not pid_file.exists():
232
+ print(json.dumps({"status": "not_running"}))
233
+ return 0
234
+
235
+ pid = int(pid_file.read_text().strip())
236
+
237
+ # Kill the entire process group (node + Chromium + Vite) to avoid orphans
238
+ try:
239
+ os.killpg(pid, signal.SIGTERM)
240
+ except ProcessLookupError:
241
+ pass
242
+ except PermissionError:
243
+ # Fallback: kill just the main process if group kill fails
244
+ try:
245
+ os.kill(pid, signal.SIGTERM)
246
+ except ProcessLookupError:
247
+ pass
248
+
249
+ # Wait briefly for graceful shutdown (browser.close() in bridge)
250
+ time.sleep(1)
251
+
252
+ # Force-kill any remaining processes in the group
253
+ try:
254
+ os.killpg(pid, signal.SIGKILL)
255
+ except (ProcessLookupError, PermissionError):
256
+ pass
257
+
258
+ pid_file.unlink(missing_ok=True)
259
+
260
+ # --- Post-stop: filter errors + extract context ---
261
+ runtime_log = tel_dir / "runtime.log"
262
+ error_log = tel_dir / "runtime_error.log"
263
+ context_file = tel_dir / "runtime_context.json"
264
+
265
+ errors = _filter_errors(runtime_log, error_log)
266
+ _extract_context(runtime_log, context_file)
267
+
268
+ print(json.dumps({
269
+ "status": "stopped",
270
+ "errors_found": len(errors),
271
+ }))
272
+ return 0
273
+
274
+
275
+ def main() -> int:
276
+ ap = argparse.ArgumentParser(description="Runtime telemetry collector (start/stop)")
277
+ ap.add_argument("--mode", required=True, choices=["start", "stop"])
278
+ ap.add_argument("--platform", choices=["web", "ios", "android"], default="web")
279
+ ap.add_argument("--workspace", required=True, help="Path to the user's project")
280
+ ap.add_argument("--connect", default=None,
281
+ help="CDP endpoint to connect to existing browser (e.g. http://localhost:9222)")
282
+ args = ap.parse_args()
283
+
284
+ workspace = Path(args.workspace).resolve()
285
+
286
+ if args.mode == "start":
287
+ return _start(args.platform, workspace, connect=args.connect)
288
+ else:
289
+ return _stop(workspace)
290
+
291
+
292
+ if __name__ == "__main__":
293
+ sys.exit(main())