@mentra/engine 3.2.0-dev.221 → 3.2.0-dev.223

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 (69) hide show
  1. package/build/generated/releaseMetadata.js +5 -5
  2. package/build/generated/releaseMetadata.js.map +1 -1
  3. package/build/index.d.ts +2 -0
  4. package/build/index.d.ts.map +1 -1
  5. package/build/index.js +1 -0
  6. package/build/index.js.map +1 -1
  7. package/build/services/AcsMeetingService.d.ts +360 -10
  8. package/build/services/AcsMeetingService.d.ts.map +1 -1
  9. package/build/services/AcsMeetingService.js +836 -19
  10. package/build/services/AcsMeetingService.js.map +1 -1
  11. package/build/services/AudioPlaybackService.d.ts +6 -0
  12. package/build/services/AudioPlaybackService.d.ts.map +1 -1
  13. package/build/services/AudioPlaybackService.js +4 -3
  14. package/build/services/AudioPlaybackService.js.map +1 -1
  15. package/build/services/GlassesMicProbe.d.ts +78 -0
  16. package/build/services/GlassesMicProbe.d.ts.map +1 -0
  17. package/build/services/GlassesMicProbe.js +258 -0
  18. package/build/services/GlassesMicProbe.js.map +1 -0
  19. package/build/services/LocalMiniappRuntime.d.ts +83 -0
  20. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  21. package/build/services/LocalMiniappRuntime.js +714 -39
  22. package/build/services/LocalMiniappRuntime.js.map +1 -1
  23. package/build/services/MentraJSLogPipeline.d.ts +39 -0
  24. package/build/services/MentraJSLogPipeline.d.ts.map +1 -1
  25. package/build/services/MentraJSLogPipeline.js +59 -3
  26. package/build/services/MentraJSLogPipeline.js.map +1 -1
  27. package/build/services/MentraJSRouter.d.ts +1 -1
  28. package/build/services/MentraJSRouter.d.ts.map +1 -1
  29. package/build/services/MentraJSRouter.js +2 -2
  30. package/build/services/MentraJSRouter.js.map +1 -1
  31. package/build/services/MicStateCoordinator.d.ts +22 -0
  32. package/build/services/MicStateCoordinator.d.ts.map +1 -1
  33. package/build/services/MicStateCoordinator.js +34 -3
  34. package/build/services/MicStateCoordinator.js.map +1 -1
  35. package/build/services/PhoneStreamCoordinator.d.ts +8 -0
  36. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  37. package/build/services/PhoneStreamCoordinator.js +2 -0
  38. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  39. package/build/services/SoftapCallTransport.d.ts +365 -0
  40. package/build/services/SoftapCallTransport.d.ts.map +1 -0
  41. package/build/services/SoftapCallTransport.js +722 -0
  42. package/build/services/SoftapCallTransport.js.map +1 -0
  43. package/build/services/SoftapCleanupBarrier.d.ts +45 -0
  44. package/build/services/SoftapCleanupBarrier.d.ts.map +1 -0
  45. package/build/services/SoftapCleanupBarrier.js +51 -0
  46. package/build/services/SoftapCleanupBarrier.js.map +1 -0
  47. package/build/utils/pcm16.d.ts +35 -0
  48. package/build/utils/pcm16.d.ts.map +1 -0
  49. package/build/utils/pcm16.js +85 -0
  50. package/build/utils/pcm16.js.map +1 -0
  51. package/build/utils/softapTrace.d.ts +39 -0
  52. package/build/utils/softapTrace.d.ts.map +1 -0
  53. package/build/utils/softapTrace.js +124 -0
  54. package/build/utils/softapTrace.js.map +1 -0
  55. package/package.json +7 -7
  56. package/src/generated/releaseMetadata.ts +5 -5
  57. package/src/index.ts +2 -0
  58. package/src/services/AcsMeetingService.ts +956 -22
  59. package/src/services/AudioPlaybackService.ts +12 -3
  60. package/src/services/GlassesMicProbe.ts +300 -0
  61. package/src/services/LocalMiniappRuntime.ts +775 -42
  62. package/src/services/MentraJSLogPipeline.ts +70 -3
  63. package/src/services/MentraJSRouter.ts +2 -2
  64. package/src/services/MicStateCoordinator.ts +35 -3
  65. package/src/services/PhoneStreamCoordinator.ts +10 -0
  66. package/src/services/SoftapCallTransport.ts +928 -0
  67. package/src/services/SoftapCleanupBarrier.ts +72 -0
  68. package/src/utils/pcm16.ts +93 -0
  69. package/src/utils/softapTrace.ts +133 -0
@@ -49,12 +49,42 @@ export interface ThrottleOptions {
49
49
  /** Clock override for tests. */
50
50
  now?: () => number;
51
51
  }
52
+ /** A per-package override of the shared budget. Both fields are absolute, not multipliers. */
53
+ export interface PackageLogBudget {
54
+ tokensPerSecond: number;
55
+ bucketCapacity: number;
56
+ }
57
+ /**
58
+ * Budgets for miniapps under active diagnosis.
59
+ *
60
+ * The default ceiling protects the host from a miniapp that logs in a render loop, and it is the
61
+ * right default. It is the wrong ceiling for a miniapp someone is actively debugging: a single
62
+ * instrumented call join emits several hundred lines in its first few seconds, which exhausts the
63
+ * burst and then drops everything that follows — and the drops land exactly where the interesting
64
+ * part is. A log with silent holes in the failure window is worse than no log, because it is read
65
+ * as complete.
66
+ *
67
+ * So the ceiling is raised for named packages rather than lowered for everyone. Anything not
68
+ * listed here keeps the protective default.
69
+ */
70
+ export declare const DIAGNOSTIC_LOG_BUDGETS: Readonly<Record<string, PackageLogBudget>>;
71
+ /** Ring-buffer window for a package in {@link DIAGNOSTIC_LOG_BUDGETS}. See `capacityFor`. */
72
+ export declare const DIAGNOSTIC_RING_CAPACITY = 2000;
52
73
  export declare class MentraJSLogThrottle {
53
74
  private readonly tokensPerSecond;
54
75
  private readonly bucketCapacity;
55
76
  private readonly now;
56
77
  private readonly buckets;
78
+ private readonly budgets;
57
79
  constructor(opts?: ThrottleOptions);
80
+ /**
81
+ * Raise (or lower) one package's budget at runtime.
82
+ *
83
+ * Exists so a support session can widen the pipe for the miniapp being investigated without a
84
+ * rebuild, and so tests can exercise a budget without depending on the shipped table.
85
+ */
86
+ setPackageBudget(packageName: string, budget: PackageLogBudget | null): void;
87
+ private budgetFor;
58
88
  /**
59
89
  * Try to consume one token for the named package. Returns either:
60
90
  * - `{allowed: true}` — caller emits the original log line.
@@ -80,6 +110,15 @@ export declare class MentraJSLogRingBuffer {
80
110
  private readonly capacity;
81
111
  private readonly buffers;
82
112
  constructor(capacityPerPackage?: number);
113
+ /**
114
+ * Capacity for a package under diagnosis.
115
+ *
116
+ * This buffer is the miniapp's last words in a crash report, and 200 lines of a heavily
117
+ * instrumented miniapp is a couple of seconds — so the report would arrive holding the
118
+ * aftermath and none of the cause. A package with a raised log budget needs a window long
119
+ * enough to contain whatever produced the crash.
120
+ */
121
+ private capacityFor;
83
122
  push(packageName: string, line: string): void;
84
123
  snapshot(packageName: string): string[];
85
124
  clear(packageName: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"MentraJSLogPipeline.d.ts","sourceRoot":"","sources":["../../src/services/MentraJSLogPipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBrD;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AASD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;gBAEpD,IAAI,GAAE,eAAoB;IAMtC;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG;QAAC,OAAO,EAAE,IAAI,CAAA;KAAC,GAAG;QAAC,OAAO,EAAE,KAAK,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAC;IA4BxF,oCAAoC;IACpC,aAAa,IAAI,IAAI;CAGtB;AAED;;;GAGG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;gBAE/C,kBAAkB,SAAM;IAIpC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAU7C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE;IAIvC,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;CAGjC"}
1
+ {"version":3,"file":"MentraJSLogPipeline.d.ts","sourceRoot":"","sources":["../../src/services/MentraJSLogPipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBrD;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,8FAA8F;AAC9F,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAI7E,CAAA;AAED,6FAA6F;AAC7F,eAAO,MAAM,wBAAwB,OAAQ,CAAA;AAS7C,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiF;gBAE7F,IAAI,GAAE,eAAoB;IAMtC;;;;;OAKG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI;IAQ5E,OAAO,CAAC,SAAS;IASjB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG;QAAC,OAAO,EAAE,IAAI,CAAA;KAAC,GAAG;QAAC,OAAO,EAAE,KAAK,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAC;IA6BxF,oCAAoC;IACpC,aAAa,IAAI,IAAI;CAGtB;AAED;;;GAGG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;gBAE/C,kBAAkB,SAAM;IAIpC;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAKnB,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAW7C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE;IAIvC,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;CAGjC"}
@@ -56,16 +56,58 @@ export function redactSecrets(value) {
56
56
  }
57
57
  return value;
58
58
  }
59
+ /**
60
+ * Budgets for miniapps under active diagnosis.
61
+ *
62
+ * The default ceiling protects the host from a miniapp that logs in a render loop, and it is the
63
+ * right default. It is the wrong ceiling for a miniapp someone is actively debugging: a single
64
+ * instrumented call join emits several hundred lines in its first few seconds, which exhausts the
65
+ * burst and then drops everything that follows — and the drops land exactly where the interesting
66
+ * part is. A log with silent holes in the failure window is worse than no log, because it is read
67
+ * as complete.
68
+ *
69
+ * So the ceiling is raised for named packages rather than lowered for everyone. Anything not
70
+ * listed here keeps the protective default.
71
+ */
72
+ export const DIAGNOSTIC_LOG_BUDGETS = {
73
+ // Mentra Call is instrumented end to end — UI, background, and the host call path — so the
74
+ // whole of a join, a cancel, and a teardown can be read back from one capture.
75
+ "com.mentra.call": { tokensPerSecond: 200, bucketCapacity: 5_000 },
76
+ };
77
+ /** Ring-buffer window for a package in {@link DIAGNOSTIC_LOG_BUDGETS}. See `capacityFor`. */
78
+ export const DIAGNOSTIC_RING_CAPACITY = 2_000;
59
79
  export class MentraJSLogThrottle {
60
80
  tokensPerSecond;
61
81
  bucketCapacity;
62
82
  now;
63
83
  buckets = new Map();
84
+ budgets = new Map(Object.entries(DIAGNOSTIC_LOG_BUDGETS));
64
85
  constructor(opts = {}) {
65
86
  this.tokensPerSecond = opts.tokensPerSecond ?? 100 / 60;
66
87
  this.bucketCapacity = opts.bucketCapacity ?? 500;
67
88
  this.now = opts.now ?? (() => Date.now());
68
89
  }
90
+ /**
91
+ * Raise (or lower) one package's budget at runtime.
92
+ *
93
+ * Exists so a support session can widen the pipe for the miniapp being investigated without a
94
+ * rebuild, and so tests can exercise a budget without depending on the shipped table.
95
+ */
96
+ setPackageBudget(packageName, budget) {
97
+ if (budget)
98
+ this.budgets.set(packageName, budget);
99
+ else
100
+ this.budgets.delete(packageName);
101
+ // The bucket holds a capacity snapshot, so a stale one would keep enforcing the old ceiling
102
+ // until it happened to refill. Dropping it re-derives from the new budget on the next line.
103
+ this.buckets.delete(packageName);
104
+ }
105
+ budgetFor(packageName) {
106
+ return (this.budgets.get(packageName) ?? {
107
+ tokensPerSecond: this.tokensPerSecond,
108
+ bucketCapacity: this.bucketCapacity,
109
+ });
110
+ }
69
111
  /**
70
112
  * Try to consume one token for the named package. Returns either:
71
113
  * - `{allowed: true}` — caller emits the original log line.
@@ -76,10 +118,11 @@ export class MentraJSLogThrottle {
76
118
  */
77
119
  consume(packageName) {
78
120
  const at = this.now();
121
+ const budget = this.budgetFor(packageName);
79
122
  let bucket = this.buckets.get(packageName);
80
123
  if (!bucket) {
81
124
  bucket = {
82
- tokens: this.bucketCapacity,
125
+ tokens: budget.bucketCapacity,
83
126
  lastRefillAtMs: at,
84
127
  pendingDrops: 0,
85
128
  };
@@ -87,7 +130,7 @@ export class MentraJSLogThrottle {
87
130
  }
88
131
  else {
89
132
  const elapsedSec = (at - bucket.lastRefillAtMs) / 1000;
90
- bucket.tokens = Math.min(this.bucketCapacity, bucket.tokens + elapsedSec * this.tokensPerSecond);
133
+ bucket.tokens = Math.min(budget.bucketCapacity, bucket.tokens + elapsedSec * budget.tokensPerSecond);
91
134
  bucket.lastRefillAtMs = at;
92
135
  }
93
136
  if (bucket.tokens >= 1) {
@@ -117,6 +160,18 @@ export class MentraJSLogRingBuffer {
117
160
  constructor(capacityPerPackage = 200) {
118
161
  this.capacity = capacityPerPackage;
119
162
  }
163
+ /**
164
+ * Capacity for a package under diagnosis.
165
+ *
166
+ * This buffer is the miniapp's last words in a crash report, and 200 lines of a heavily
167
+ * instrumented miniapp is a couple of seconds — so the report would arrive holding the
168
+ * aftermath and none of the cause. A package with a raised log budget needs a window long
169
+ * enough to contain whatever produced the crash.
170
+ */
171
+ capacityFor(packageName) {
172
+ const budget = DIAGNOSTIC_LOG_BUDGETS[packageName];
173
+ return budget ? Math.max(this.capacity, DIAGNOSTIC_RING_CAPACITY) : this.capacity;
174
+ }
120
175
  push(packageName, line) {
121
176
  let buf = this.buffers.get(packageName);
122
177
  if (!buf) {
@@ -124,7 +179,8 @@ export class MentraJSLogRingBuffer {
124
179
  this.buffers.set(packageName, buf);
125
180
  }
126
181
  buf.push(line);
127
- if (buf.length > this.capacity)
182
+ const capacity = this.capacityFor(packageName);
183
+ while (buf.length > capacity)
128
184
  buf.shift();
129
185
  }
130
186
  snapshot(packageName) {
@@ -1 +1 @@
1
- {"version":3,"file":"MentraJSLogPipeline.js","sourceRoot":"","sources":["../../src/services/MentraJSLogPipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,kBAAkB,GAAG,0DAA0D,CAAA;AAErF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAA;IAC9D,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3C,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,GAAG,GAA4B,EAAE,CAAA;QACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACtE,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/B,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,CAAA;YACvB,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;YAC3B,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAwBD,MAAM,OAAO,mBAAmB;IACb,eAAe,CAAQ;IACvB,cAAc,CAAQ;IACtB,GAAG,CAAc;IACjB,OAAO,GAA+B,IAAI,GAAG,EAAE,CAAA;IAEhE,YAAY,OAAwB,EAAE;QACpC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,IAAI,GAAG,GAAG,EAAE,CAAA;QACvD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,GAAG,CAAA;QAChD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAC3C,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,WAAmB;QACzB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACrB,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG;gBACP,MAAM,EAAE,IAAI,CAAC,cAAc;gBAC3B,cAAc,EAAE,EAAE;gBAClB,YAAY,EAAE,CAAC;aAChB,CAAA;YACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;QACvC,CAAC;aAAM,CAAC;YACN,MAAM,UAAU,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,IAAI,CAAA;YACtD,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,CAAA;YAChG,MAAM,CAAC,cAAc,GAAG,EAAE,CAAA;QAC5B,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,MAAM,IAAI,CAAC,CAAA;YAClB,IAAI,MAAM,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAA;gBACnC,MAAM,CAAC,YAAY,GAAG,CAAC,CAAA;gBACvB,OAAO,EAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,cAAc,OAAO,GAAG,EAAC,CAAA;YAClE,CAAC;YACD,OAAO,EAAC,OAAO,EAAE,IAAI,EAAC,CAAA;QACxB,CAAC;QACD,MAAM,CAAC,YAAY,IAAI,CAAC,CAAA;QACxB,OAAO,EAAC,OAAO,EAAE,KAAK,EAAC,CAAA;IACzB,CAAC;IAED,oCAAoC;IACpC,aAAa;QACX,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;IACtB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IACf,QAAQ,CAAQ;IAChB,OAAO,GAA0B,IAAI,GAAG,EAAE,CAAA;IAE3D,YAAY,kBAAkB,GAAG,GAAG;QAClC,IAAI,CAAC,QAAQ,GAAG,kBAAkB,CAAA;IACpC,CAAC;IAED,IAAI,CAAC,WAAmB,EAAE,IAAY;QACpC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACvC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,EAAE,CAAA;YACR,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAA;QACpC,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;YAAE,GAAG,CAAC,KAAK,EAAE,CAAA;IAC7C,CAAC;IAED,QAAQ,CAAC,WAAmB;QAC1B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IACnD,CAAC;IAED,KAAK,CAAC,WAAmB;QACvB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAClC,CAAC;CACF","sourcesContent":["/**\n * MentraJSLogPipeline — the three log-side utilities that\n * MentraJSRouter's `__log` handler runs every miniapp log line through:\n *\n * 1. redactSecrets — walks a value (string / array / object) and\n * replaces anything matching the secret-key regex\n * (token|password|secret|auth|bearer|key|api[_-]?key) with\n * \"[REDACTED]\". Conservative: over-redaction is preferable to\n * leaking a real secret into Sentry breadcrumbs.\n *\n * 2. MentraJSLogThrottle — token-bucket rate limiter, 100 lines/min\n * sustained with a 500-line burst, per miniapp. Excess is dropped\n * and surfaced once as a `[throttled N]` summary. Stops a\n * misbehaving miniapp from drowning the host's log stream.\n *\n * 3. MentraJSLogRingBuffer — fixed-size circular buffer (default 200)\n * holding the most recent log lines per miniapp. Dumped into the\n * crash report so post-mortem tooling sees the miniapp's last words.\n *\n * All three are pure (no React Native imports) and unit-tested in\n * MentraJSLogPipeline.test.ts.\n */\n\nconst SECRET_KEY_PATTERN = /\\b(token|password|secret|auth|bearer|key|api[_-]?key)\\b/i\n\n/**\n * Redact obvious secret-like strings from a log line. Walks the JSON\n * argument list, replacing any value that looks like a secret with\n * `[REDACTED]`. Conservative — false negatives are fine, false positives\n * are bad (devs lose visibility into their own data).\n *\n * Heuristics:\n * 1. Top-level strings matching SECRET_KEY_PATTERN get the entire value\n * redacted (e.g. `console.log(\"token=abc123\")` → `[REDACTED]`).\n * 2. Object keys matching the pattern get their values replaced.\n * 3. Arrays are walked element-by-element.\n *\n * Returns a new structure — never mutates the input.\n */\nexport function redactSecrets(value: unknown): unknown {\n if (typeof value === \"string\") {\n return SECRET_KEY_PATTERN.test(value) ? \"[REDACTED]\" : value\n }\n if (Array.isArray(value)) {\n return value.map((v) => redactSecrets(v))\n }\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (SECRET_KEY_PATTERN.test(k)) {\n out[k] = \"[REDACTED]\"\n } else {\n out[k] = redactSecrets(v)\n }\n }\n return out\n }\n return value\n}\n\n/**\n * Token bucket throttler. Per-package: 100 logs/min sustained with a 500\n * burst. Excess lines are dropped; the first drop in each refill window\n * emits a `[throttled N]` synthetic line so the dev knows messages\n * were lost.\n */\nexport interface ThrottleOptions {\n /** Tokens added per second. Default 100/60 = ~1.67. */\n tokensPerSecond?: number\n /** Bucket capacity. Default 500. */\n bucketCapacity?: number\n /** Clock override for tests. */\n now?: () => number\n}\n\ninterface PackageBucket {\n tokens: number\n lastRefillAtMs: number\n /** Drops accumulated since the last `[throttled N]` synthetic line. */\n pendingDrops: number\n}\n\nexport class MentraJSLogThrottle {\n private readonly tokensPerSecond: number\n private readonly bucketCapacity: number\n private readonly now: () => number\n private readonly buckets: Map<string, PackageBucket> = new Map()\n\n constructor(opts: ThrottleOptions = {}) {\n this.tokensPerSecond = opts.tokensPerSecond ?? 100 / 60\n this.bucketCapacity = opts.bucketCapacity ?? 500\n this.now = opts.now ?? (() => Date.now())\n }\n\n /**\n * Try to consume one token for the named package. Returns either:\n * - `{allowed: true}` — caller emits the original log line.\n * - `{allowed: false, throttledLine}` — caller may emit the\n * synthetic throttled line (the first drop in each window).\n * If `throttledLine` is undefined the caller drops silently\n * (subsequent drops in the same window).\n */\n consume(packageName: string): {allowed: true} | {allowed: false; throttledLine?: string} {\n const at = this.now()\n let bucket = this.buckets.get(packageName)\n if (!bucket) {\n bucket = {\n tokens: this.bucketCapacity,\n lastRefillAtMs: at,\n pendingDrops: 0,\n }\n this.buckets.set(packageName, bucket)\n } else {\n const elapsedSec = (at - bucket.lastRefillAtMs) / 1000\n bucket.tokens = Math.min(this.bucketCapacity, bucket.tokens + elapsedSec * this.tokensPerSecond)\n bucket.lastRefillAtMs = at\n }\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1\n if (bucket.pendingDrops > 0) {\n const dropped = bucket.pendingDrops\n bucket.pendingDrops = 0\n return {allowed: false, throttledLine: `[throttled ${dropped}]`}\n }\n return {allowed: true}\n }\n bucket.pendingDrops += 1\n return {allowed: false}\n }\n\n /** Test/dev — reset all buckets. */\n resetForTests(): void {\n this.buckets.clear()\n }\n}\n\n/**\n * In-memory ring buffer holding the last N lines per package. Useful as a\n * \"last words\" context attached to crash reports.\n */\nexport class MentraJSLogRingBuffer {\n private readonly capacity: number\n private readonly buffers: Map<string, string[]> = new Map()\n\n constructor(capacityPerPackage = 200) {\n this.capacity = capacityPerPackage\n }\n\n push(packageName: string, line: string): void {\n let buf = this.buffers.get(packageName)\n if (!buf) {\n buf = []\n this.buffers.set(packageName, buf)\n }\n buf.push(line)\n if (buf.length > this.capacity) buf.shift()\n }\n\n snapshot(packageName: string): string[] {\n return [...(this.buffers.get(packageName) ?? [])]\n }\n\n clear(packageName: string): void {\n this.buffers.delete(packageName)\n }\n}\n"]}
1
+ {"version":3,"file":"MentraJSLogPipeline.js","sourceRoot":"","sources":["../../src/services/MentraJSLogPipeline.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MentraJSLogPipeline — the three log-side utilities that\n * MentraJSRouter's `__log` handler runs every miniapp log line through:\n *\n * 1. redactSecrets — walks a value (string / array / object) and\n * replaces anything matching the secret-key regex\n * (token|password|secret|auth|bearer|key|api[_-]?key) with\n * \"[REDACTED]\". Conservative: over-redaction is preferable to\n * leaking a real secret into Sentry breadcrumbs.\n *\n * 2. MentraJSLogThrottle — token-bucket rate limiter, 100 lines/min\n * sustained with a 500-line burst, per miniapp. Excess is dropped\n * and surfaced once as a `[throttled N]` summary. Stops a\n * misbehaving miniapp from drowning the host's log stream.\n *\n * 3. MentraJSLogRingBuffer — fixed-size circular buffer (default 200)\n * holding the most recent log lines per miniapp. Dumped into the\n * crash report so post-mortem tooling sees the miniapp's last words.\n *\n * All three are pure (no React Native imports) and unit-tested in\n * MentraJSLogPipeline.test.ts.\n */\n\nconst SECRET_KEY_PATTERN = /\\b(token|password|secret|auth|bearer|key|api[_-]?key)\\b/i\n\n/**\n * Redact obvious secret-like strings from a log line. Walks the JSON\n * argument list, replacing any value that looks like a secret with\n * `[REDACTED]`. Conservative — false negatives are fine, false positives\n * are bad (devs lose visibility into their own data).\n *\n * Heuristics:\n * 1. Top-level strings matching SECRET_KEY_PATTERN get the entire value\n * redacted (e.g. `console.log(\"token=abc123\")` → `[REDACTED]`).\n * 2. Object keys matching the pattern get their values replaced.\n * 3. Arrays are walked element-by-element.\n *\n * Returns a new structure — never mutates the input.\n */\nexport function redactSecrets(value: unknown): unknown {\n if (typeof value === \"string\") {\n return SECRET_KEY_PATTERN.test(value) ? \"[REDACTED]\" : value\n }\n if (Array.isArray(value)) {\n return value.map((v) => redactSecrets(v))\n }\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (SECRET_KEY_PATTERN.test(k)) {\n out[k] = \"[REDACTED]\"\n } else {\n out[k] = redactSecrets(v)\n }\n }\n return out\n }\n return value\n}\n\n/**\n * Token bucket throttler. Per-package: 100 logs/min sustained with a 500\n * burst. Excess lines are dropped; the first drop in each refill window\n * emits a `[throttled N]` synthetic line so the dev knows messages\n * were lost.\n */\nexport interface ThrottleOptions {\n /** Tokens added per second. Default 100/60 = ~1.67. */\n tokensPerSecond?: number\n /** Bucket capacity. Default 500. */\n bucketCapacity?: number\n /** Clock override for tests. */\n now?: () => number\n}\n\n/** A per-package override of the shared budget. Both fields are absolute, not multipliers. */\nexport interface PackageLogBudget {\n tokensPerSecond: number\n bucketCapacity: number\n}\n\n/**\n * Budgets for miniapps under active diagnosis.\n *\n * The default ceiling protects the host from a miniapp that logs in a render loop, and it is the\n * right default. It is the wrong ceiling for a miniapp someone is actively debugging: a single\n * instrumented call join emits several hundred lines in its first few seconds, which exhausts the\n * burst and then drops everything that follows — and the drops land exactly where the interesting\n * part is. A log with silent holes in the failure window is worse than no log, because it is read\n * as complete.\n *\n * So the ceiling is raised for named packages rather than lowered for everyone. Anything not\n * listed here keeps the protective default.\n */\nexport const DIAGNOSTIC_LOG_BUDGETS: Readonly<Record<string, PackageLogBudget>> = {\n // Mentra Call is instrumented end to end — UI, background, and the host call path — so the\n // whole of a join, a cancel, and a teardown can be read back from one capture.\n \"com.mentra.call\": {tokensPerSecond: 200, bucketCapacity: 5_000},\n}\n\n/** Ring-buffer window for a package in {@link DIAGNOSTIC_LOG_BUDGETS}. See `capacityFor`. */\nexport const DIAGNOSTIC_RING_CAPACITY = 2_000\n\ninterface PackageBucket {\n tokens: number\n lastRefillAtMs: number\n /** Drops accumulated since the last `[throttled N]` synthetic line. */\n pendingDrops: number\n}\n\nexport class MentraJSLogThrottle {\n private readonly tokensPerSecond: number\n private readonly bucketCapacity: number\n private readonly now: () => number\n private readonly buckets: Map<string, PackageBucket> = new Map()\n private readonly budgets: Map<string, PackageLogBudget> = new Map(Object.entries(DIAGNOSTIC_LOG_BUDGETS))\n\n constructor(opts: ThrottleOptions = {}) {\n this.tokensPerSecond = opts.tokensPerSecond ?? 100 / 60\n this.bucketCapacity = opts.bucketCapacity ?? 500\n this.now = opts.now ?? (() => Date.now())\n }\n\n /**\n * Raise (or lower) one package's budget at runtime.\n *\n * Exists so a support session can widen the pipe for the miniapp being investigated without a\n * rebuild, and so tests can exercise a budget without depending on the shipped table.\n */\n setPackageBudget(packageName: string, budget: PackageLogBudget | null): void {\n if (budget) this.budgets.set(packageName, budget)\n else this.budgets.delete(packageName)\n // The bucket holds a capacity snapshot, so a stale one would keep enforcing the old ceiling\n // until it happened to refill. Dropping it re-derives from the new budget on the next line.\n this.buckets.delete(packageName)\n }\n\n private budgetFor(packageName: string): PackageLogBudget {\n return (\n this.budgets.get(packageName) ?? {\n tokensPerSecond: this.tokensPerSecond,\n bucketCapacity: this.bucketCapacity,\n }\n )\n }\n\n /**\n * Try to consume one token for the named package. Returns either:\n * - `{allowed: true}` — caller emits the original log line.\n * - `{allowed: false, throttledLine}` — caller may emit the\n * synthetic throttled line (the first drop in each window).\n * If `throttledLine` is undefined the caller drops silently\n * (subsequent drops in the same window).\n */\n consume(packageName: string): {allowed: true} | {allowed: false; throttledLine?: string} {\n const at = this.now()\n const budget = this.budgetFor(packageName)\n let bucket = this.buckets.get(packageName)\n if (!bucket) {\n bucket = {\n tokens: budget.bucketCapacity,\n lastRefillAtMs: at,\n pendingDrops: 0,\n }\n this.buckets.set(packageName, bucket)\n } else {\n const elapsedSec = (at - bucket.lastRefillAtMs) / 1000\n bucket.tokens = Math.min(budget.bucketCapacity, bucket.tokens + elapsedSec * budget.tokensPerSecond)\n bucket.lastRefillAtMs = at\n }\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1\n if (bucket.pendingDrops > 0) {\n const dropped = bucket.pendingDrops\n bucket.pendingDrops = 0\n return {allowed: false, throttledLine: `[throttled ${dropped}]`}\n }\n return {allowed: true}\n }\n bucket.pendingDrops += 1\n return {allowed: false}\n }\n\n /** Test/dev — reset all buckets. */\n resetForTests(): void {\n this.buckets.clear()\n }\n}\n\n/**\n * In-memory ring buffer holding the last N lines per package. Useful as a\n * \"last words\" context attached to crash reports.\n */\nexport class MentraJSLogRingBuffer {\n private readonly capacity: number\n private readonly buffers: Map<string, string[]> = new Map()\n\n constructor(capacityPerPackage = 200) {\n this.capacity = capacityPerPackage\n }\n\n /**\n * Capacity for a package under diagnosis.\n *\n * This buffer is the miniapp's last words in a crash report, and 200 lines of a heavily\n * instrumented miniapp is a couple of seconds — so the report would arrive holding the\n * aftermath and none of the cause. A package with a raised log budget needs a window long\n * enough to contain whatever produced the crash.\n */\n private capacityFor(packageName: string): number {\n const budget = DIAGNOSTIC_LOG_BUDGETS[packageName]\n return budget ? Math.max(this.capacity, DIAGNOSTIC_RING_CAPACITY) : this.capacity\n }\n\n push(packageName: string, line: string): void {\n let buf = this.buffers.get(packageName)\n if (!buf) {\n buf = []\n this.buffers.set(packageName, buf)\n }\n buf.push(line)\n const capacity = this.capacityFor(packageName)\n while (buf.length > capacity) buf.shift()\n }\n\n snapshot(packageName: string): string[] {\n return [...(this.buffers.get(packageName) ?? [])]\n }\n\n clear(packageName: string): void {\n this.buffers.delete(packageName)\n }\n}\n"]}
@@ -170,7 +170,7 @@ export declare class MentraJSRouter {
170
170
  * router gates this to registered packages and keeps LocalMiniappView from
171
171
  * reaching into runtime internals.
172
172
  */
173
- probeForegroundLiveness(packageName: string, reason?: string): void;
173
+ probeForegroundLiveness(packageName: string, reason?: string, timeoutMs?: number): void;
174
174
  private handleOutbound;
175
175
  /**
176
176
  * The wire format for `__bridge.send` is `argsJson = JSON.stringify([raw])`
@@ -1 +1 @@
1
- {"version":3,"file":"MentraJSRouter.d.ts","sourceRoot":"","sources":["../../src/services/MentraJSRouter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MentraJSRouter.d.ts","sourceRoot":"","sources":["../../src/services/MentraJSRouter.ts"],"names":[],"mappings":"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"}
@@ -306,10 +306,10 @@ export class MentraJSRouter {
306
306
  * router gates this to registered packages and keeps LocalMiniappView from
307
307
  * reaching into runtime internals.
308
308
  */
309
- probeForegroundLiveness(packageName, reason = "foreground-open") {
309
+ probeForegroundLiveness(packageName, reason = "foreground-open", timeoutMs) {
310
310
  if (!this.registered.has(packageName))
311
311
  return;
312
- this.runtime.probeForegroundLiveness(packageName, reason);
312
+ this.runtime.probeForegroundLiveness(packageName, reason, timeoutMs);
313
313
  }
314
314
  // ----------------------------------------------------------------
315
315
  // Outbound message handling
@@ -1 +1 @@
1
- {"version":3,"file":"MentraJSRouter.js","sourceRoot":"","sources":["../../src/services/MentraJSRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview MentraJSRouter — RN-side host that bridges the per-miniapp\n * JS contexts (iOS-JSC and Android-QuickJS via Zipline) into the existing\n * {@link LocalMiniappRuntime} handler set.\n *\n * Rather than re-implement the 33 dispatch arms inline, the router\n * consumes Crust's `mentrajs_message` Expo event and forwards its\n * `__bridge.send(raw)` payloads to {@link LocalMiniappRuntime.handleRawMessage}.\n * The SDK envelope shape (`{type: \"DISPLAY\", ...}` etc.) is unchanged from\n * the WebView path, so every existing handler runs verbatim. What changes\n * is the *transport*:\n *\n * - **Inbound** (background JS → host): `__dispatch(\"__bridge\", \"send\",\n * [rawJsonString])` → `mentrajs_message` event → router →\n * `LocalMiniappRuntime.handleRawMessage(packageName, raw)`.\n * - **Outbound** (host → background JS): every package registered with\n * the router gets a `sendMessage(raw)` fn that calls\n * `Crust.mentraJsDispatchToJs(packageName, {kind: \"bridge\", raw})`.\n * The polyfill's `__deliver` recognises `kind === \"bridge\"` and calls\n * `__mentraDeliverBridgeRaw(raw)`, which routes into the SDK's\n * {@link DispatchTransport.onMessage} handler.\n *\n * Host-side handler bodies (display fan-out, mic state, transcription,\n * navigation, etc.) live untouched in `LocalMiniappRuntime.ts`.\n *\n * Cloud-message routing (`phone_stream_status`, `phone_managed_stream_status`)\n * goes straight through `LocalMiniappRuntime.handleCloudMessage` — nothing\n * for the router to do; the responses arrive inside an envelope whose\n * `sendMessage` was already registered via the same path here.\n *\n * The router also bridges native error / log / unhandled-rejection\n * events (`iface: \"__log\"`, `iface: \"__error\"`) into the standard\n * `console.*` + Sentry breadcrumb pipeline so miniapp telemetry is\n * visible without bringing up a separate sink.\n */\n\nimport type {EventSubscription} from \"expo-modules-core\"\n\nimport devServerBridge from \"./DevServerBridge\"\nimport type localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport type {InstalledMiniappManifest} from \"./LocalMiniappRuntime\"\nimport type {MentraJSCrashController} from \"./MentraJSCrashController\"\nimport {MentraJSLogRingBuffer, MentraJSLogThrottle, redactSecrets} from \"./MentraJSLogPipeline\"\nimport {islandNotifications} from \"./NotificationsEmitter\"\nimport type {MentraUIRouter} from \"./MentraUIRouter\"\nimport {miniappRunningRegistry} from \"./MiniappRunningRegistry\"\n\n/** The runtime's runtime instance type — the singleton exported from\n * LocalMiniappRuntime.ts (the file's `export default` is the instance,\n * not the class).\n */\ntype LocalMiniappRuntime = typeof localMiniappRuntime\n\n/**\n * Minimal subset of the Crust native module the router uses. Keeping the\n * binding loose lets us mock the module in unit tests without bringing\n * the whole Expo module surface along.\n */\nexport interface MentraJSCrustBinding {\n mentraJsDispatchToJs(packageName: string, envelope: Record<string, unknown>): Promise<void> | void\n mentraJsSetManifest(packageName: string, permissions: string[]): Promise<void> | void\n mentraJsLoadPolyfillBundle?: () => string\n mentraJsSpawn?: (packageName: string, polyfill: string, miniappJs: string) => Promise<boolean> | boolean\n mentraJsKill?: (packageName: string) => Promise<void> | void\n mentraJsAlivePackages?: () => string[]\n addListener: (event: string, handler: (payload: Record<string, unknown>) => void) => EventSubscription\n}\n\nexport interface OutboundMessagePayload {\n packageName: string\n iface: string\n method: string\n argsJson?: string\n args?: unknown[]\n reqId?: string\n // Implementation-internal: any extra fields the dispatcher attached on\n // .forwardToRn (e.g. payload metadata) are passed through verbatim.\n [extra: string]: unknown\n}\n\nexport type RouterLogger = {\n log: (message: string, payload?: unknown) => void\n warn: (message: string, payload?: unknown) => void\n error: (message: string, payload?: unknown) => void\n}\n\nconst defaultLogger: RouterLogger = {\n log: (m, p) => console.log(`[MentraJSRouter] ${m}`, p ?? \"\"),\n\n warn: (m, p) => console.warn(`[MentraJSRouter] ${m}`, p ?? \"\"),\n\n error: (m, p) => console.error(`[MentraJSRouter] ${m}`, p ?? \"\"),\n}\n\ninterface SpawnCache {\n miniappJs: string\n permissions: string[]\n installedManifest?: InstalledMiniappManifest\n}\n\nexport class MentraJSRouter {\n private subscription: EventSubscription | null = null\n private readonly registered: Set<string> = new Set()\n /** Cached spawn arguments so the crash controller can respawn after a backoff. */\n private readonly spawnCache: Map<string, SpawnCache> = new Map()\n /** Active respawn timers (so unregister() can cancel a pending respawn). */\n private readonly respawnTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n\n /**\n * Optional crash controller. When wired, every `__error` frame from a\n * JSContext drives the state machine; an Outcome with\n * scheduleRespawnAfterMs queues a setTimeout that re-spawns the\n * miniapp's last-known JS source after the backoff delay.\n */\n crashController: MentraJSCrashController | null = null\n\n /** Hook called when a package transitions to CRASHLOOP_DISABLED. */\n onCrashloop: ((packageName: string, reason: string) => void) | null = null\n /** Hook called on the 2nd-within-window crash (toast UX). */\n onRestartToast: ((packageName: string, reason: string) => void) | null = null\n\n /**\n * Logging plumbing. Every `__log` frame passes through `redactSecrets`\n * (token/password/etc. scrubbed), then the throttle (100/min sustained\n * per pkg, 500 burst), then lands in the ring buffer + the host\n * logger. The ring buffer holds the last 200 lines/pkg so a crash\n * report can attach \"last words\" context.\n *\n * Replace with new instances in tests; production code uses the\n * defaults.\n */\n logThrottle: MentraJSLogThrottle = new MentraJSLogThrottle()\n logRing: MentraJSLogRingBuffer = new MentraJSLogRingBuffer()\n\n /**\n * Optional UI router — when wired, `__bridge.send` envelopes whose\n * inner payload type is `UI_SEND` get routed to the bound WebView\n * instead of through LocalMiniappRuntime. Two-layer miniapps set\n * this; legacy single-bundle WebView miniapps don't and the bridge\n * frames pass through unchanged.\n */\n uiRouter: MentraUIRouter | null = null\n\n constructor(\n private readonly runtime: LocalMiniappRuntime,\n private readonly crust: MentraJSCrustBinding,\n private readonly logger: RouterLogger = defaultLogger,\n ) {}\n\n /**\n * Subscribe to Crust's `mentrajs_message` event. Idempotent — calling\n * start() twice attaches a single listener.\n */\n start(): void {\n if (this.subscription) return\n this.subscription = this.crust.addListener(\"mentrajs_message\", (raw) => {\n try {\n this.handleOutbound(raw as unknown as OutboundMessagePayload)\n } catch (e) {\n this.logger.error(`outbound handler threw`, {error: String(e), raw})\n }\n })\n // A background script unregistered for missing liveness pings is dead\n // weight: its subscriptions are gone (so the mic releases) while the\n // webview keeps rendering stale state. Route it through the same\n // respawn machinery as a crash so it comes back automatically, with\n // the crash controller's backoff + crash-loop protection.\n this.runtime.onLivenessTimeout = (packageName) => {\n this.handleCrash(packageName, \"liveness: missed pings\")\n }\n }\n\n /**\n * Detach the Crust subscription. The router can be restarted later\n * with start(); registrations survive (the runtime's app map is the\n * source of truth).\n */\n stop(): void {\n this.subscription?.remove()\n this.subscription = null\n this.runtime.onLivenessTimeout = null\n }\n\n /**\n * Register a JSContext with the runtime so its handlers can fire\n * `sendMessage(raw)` against the right context. The router builds a\n * `sendMessage` that calls `Crust.mentraJsDispatchToJs(packageName,\n * {kind: \"bridge\", raw})`; the polyfill's `__deliver` then hands the\n * envelope to `DispatchTransport.onMessage` inside the miniapp.\n *\n * Mirrors `MiniappHost.registerRuntime` on the WebView side. Callers\n * (e.g. {@link spawnAndRegister} below, or the host's miniapp launch\n * pipeline) should call this once per spawn.\n */\n registerApp(packageName: string, installedManifest?: InstalledMiniappManifest): void {\n this.runtime.registerApp(\n packageName,\n (raw: string) => {\n this.dispatchBridgeRaw(packageName, raw)\n },\n installedManifest,\n )\n this.registered.add(packageName)\n // JSContext is the source-of-truth for \"miniapp running\". The\n // home tile / tray reads this registry to project the `running`\n // flag — UI WebView open/close is separate.\n miniappRunningRegistry.add(packageName)\n }\n\n /**\n * Convenience: spawn a JSContext via Crust, register the app, and\n * set its declared permissions. Returns true on successful spawn.\n *\n * The host's miniapp launch path\n * (`mobile/src/services/miniapps/launchLocalMiniapp.ts`) calls this\n * once per JSContext mount.\n */\n async spawnAndRegister(\n packageName: string,\n miniappJs: string,\n options?: {permissions?: string[]; installedManifest?: InstalledMiniappManifest},\n ): Promise<boolean> {\n if (!this.crust.mentraJsSpawn) {\n this.logger.warn(\"mentraJsSpawn not available — host binding missing native function\")\n return false\n }\n const polyfill = this.crust.mentraJsLoadPolyfillBundle?.() ?? \"\"\n if (!polyfill) {\n this.logger.warn(`no polyfill bundle loaded for ${packageName}; spawn will likely fail`)\n }\n const ok = await this.crust.mentraJsSpawn(packageName, polyfill, miniappJs)\n if (!ok) {\n this.logger.error(`spawn failed for ${packageName}`)\n return false\n }\n // Two distinct permission gates:\n // 1. Native dispatcher (JSContext → native bridge): `mentraJsSetManifest`\n // checks per-call iface, e.g. __dispatch(\"mic\", \"start\", ...) requires\n // MICROPHONE.\n // 2. LocalMiniappRuntime (SUBSCRIBE envelope): `registerApp(...,\n // installedManifest)` carries the full manifest so SUBSCRIBE's gate\n // can match stream → permission against declared types.\n if (options?.permissions && options.permissions.length > 0) {\n await this.crust.mentraJsSetManifest(packageName, options.permissions)\n }\n this.registerApp(packageName, options?.installedManifest)\n // Cache spawn arguments so the crash controller can respawn the\n // same code after a backoff. permissions + manifest are cached too\n // because the native side resets setManifest on every spawn and\n // LocalMiniappRuntime's installedManifest entry is keyed by\n // connect-time registration.\n this.spawnCache.set(packageName, {\n miniappJs,\n permissions: options?.permissions ?? [],\n installedManifest: options?.installedManifest,\n })\n this.crashController?.onSpawn(packageName)\n\n // Fire the `init` envelope so the polyfill's __deliver handler\n // resolves `__mentraInitCallback(sessionId)`. The SDK's\n // `registerMiniapp(handler)` wires that callback to construct\n // MiniappSession and call the user's handler. Without this\n // dispatch the user's code never runs — `registerMiniapp` just\n // assigns to a global and waits.\n const sessionId = `${packageName}-${Date.now().toString(36)}`\n void this.crust.mentraJsDispatchToJs(packageName, {kind: \"init\", sessionId})\n\n return true\n }\n\n /**\n * Crash-controller integration. Called from the `__error` frame\n * handler. The controller returns an outcome that tells us whether\n * to respawn (with delay) or leave the context dead.\n */\n private handleCrash(packageName: string, reason: string): void {\n // The native context is dead the instant a crash is observed — invalidate\n // its CONNECT handshake NOW so waitForConnect() blocks through the respawn\n // backoff window instead of resolving immediately against the dead context\n // (an action invoked mid-backoff would otherwise be delivered to nothing).\n this.runtime.resetHandshake(packageName)\n const controller = this.crashController\n if (!controller) return\n const cached = this.spawnCache.get(packageName)\n if (!cached) {\n this.logger.warn(`crash for ${packageName} but no cached spawn args — cannot respawn`)\n controller.onCrash(packageName, reason)\n return\n }\n const outcome = controller.onCrash(packageName, reason)\n if (outcome.surfaceCrashloopBanner) {\n this.onCrashloop?.(packageName, reason)\n islandNotifications.emit({kind: \"miniapp_crashloop\", packageName, reason, timestamp: Date.now()})\n return\n }\n if (outcome.showRestartToast) {\n this.onRestartToast?.(packageName, reason)\n }\n if (outcome.scheduleRespawnAfterMs == null) return\n // Cancel any prior pending respawn before scheduling a new one.\n const existing = this.respawnTimers.get(packageName)\n if (existing) clearTimeout(existing)\n const timer = setTimeout(() => {\n this.respawnTimers.delete(packageName)\n void (async () => {\n try {\n await this.crust.mentraJsKill?.(packageName)\n } catch {\n /* native may have already torn down */\n }\n const ok = await this.crust.mentraJsSpawn?.(\n packageName,\n this.crust.mentraJsLoadPolyfillBundle?.() ?? \"\",\n cached.miniappJs,\n )\n if (!ok) {\n this.logger.error(`crash respawn failed for ${packageName}`)\n return\n }\n if (cached.permissions.length > 0) {\n await this.crust.mentraJsSetManifest(packageName, cached.permissions)\n }\n this.registerApp(packageName, cached.installedManifest)\n controller.onSpawn(packageName)\n // Re-fire the init envelope so the respawned context's\n // `registerMiniapp` handler runs again.\n const sessionId = `${packageName}-${Date.now().toString(36)}`\n void this.crust.mentraJsDispatchToJs(packageName, {kind: \"init\", sessionId})\n this.logger.log(`respawned ${packageName} after crash`)\n })()\n }, outcome.scheduleRespawnAfterMs)\n this.respawnTimers.set(packageName, timer)\n }\n\n private summarizeReason(payload: unknown, method: string): string {\n if (payload && typeof payload === \"object\" && \"message\" in (payload as Record<string, unknown>)) {\n return `${method}: ${(payload as {message: string}).message}`\n }\n return method\n }\n\n /**\n * Tear down a JSContext and unregister from the runtime. Synchronous\n * teardown — the spec drops the 50ms WILL_DISCONNECT grace window\n * because there's no transport handshake to flush in the JSC world.\n */\n async unregister(packageName: string): Promise<void> {\n if (!this.registered.has(packageName)) return\n // Cancel any pending crash-respawn before tearing down — we don't\n // want a stale timer to re-spawn a miniapp the user just disabled.\n const pending = this.respawnTimers.get(packageName)\n if (pending) {\n clearTimeout(pending)\n this.respawnTimers.delete(packageName)\n }\n this.spawnCache.delete(packageName)\n this.crashController?.onKill(packageName)\n this.runtime.unregisterApp(packageName)\n this.registered.delete(packageName)\n // JSContext torn down → miniapp is no longer \"running\".\n miniappRunningRegistry.remove(packageName)\n if (this.crust.mentraJsKill) {\n try {\n await this.crust.mentraJsKill(packageName)\n } catch (e) {\n this.logger.warn(`mentraJsKill threw for ${packageName}: ${String(e)}`)\n }\n }\n }\n\n /** List packages the router has registered. */\n registeredPackages(): string[] {\n return Array.from(this.registered)\n }\n\n /**\n * Validate that a reused background JSContext is still responsive when its\n * WebView comes foreground. The runtime owns the ping/timeout mechanics; the\n * router gates this to registered packages and keeps LocalMiniappView from\n * reaching into runtime internals.\n */\n probeForegroundLiveness(packageName: string, reason = \"foreground-open\"): void {\n if (!this.registered.has(packageName)) return\n this.runtime.probeForegroundLiveness(packageName, reason)\n }\n\n // ----------------------------------------------------------------\n // Outbound message handling\n // ----------------------------------------------------------------\n\n private handleOutbound(msg: OutboundMessagePayload): void {\n const {packageName, iface, method} = msg\n if (!packageName || !iface || !method) {\n this.logger.warn(`bad mentrajs_message — missing fields`, msg)\n return\n }\n\n // 1) Bridge frames: the SDK's DispatchTransport.send(raw) lands here.\n // Forward raw envelope into LocalMiniappRuntime.handleRawMessage\n // so every existing handler arm runs verbatim.\n if (iface === \"__bridge\" && method === \"send\") {\n const raw = this.coerceArgsToBridgeRaw(msg)\n if (raw == null) {\n this.logger.warn(`__bridge.send had no raw payload`, msg)\n return\n }\n // If the payload is a UI_SEND envelope, route it to the bound\n // WebView instead of LocalMiniappRuntime.\n // session.ui.send → DispatchTransport.send → here.\n if (this.uiRouter) {\n const innerPayload = this.peekBridgePayloadType(raw)\n if (innerPayload?.type === \"UI_SEND\") {\n this.uiRouter.routeFromBackground(packageName, innerPayload)\n return\n }\n }\n this.runtime.handleRawMessage(packageName, raw)\n return\n }\n\n // 2) Log frames — console.* rewired through host. We tag with the\n // packageName so Sentry breadcrumbs can be filtered downstream.\n // Pipeline:\n // raw args → redactSecrets → throttle decision → ring buffer\n // → host logger (which surfaces to dev console / Sentry)\n if (iface === \"__log\") {\n const rawArgs = this.tryParseArgs(msg.argsJson)\n const safeArgs = redactSecrets(rawArgs)\n const decision = this.logThrottle.consume(packageName)\n const tag = `[${packageName}]`\n const fn =\n method === \"warn\"\n ? this.logger.warn\n : method === \"error\" || method === \"fatal\"\n ? this.logger.error\n : this.logger.log\n // Emit a synthetic \"[throttled N]\" line in place of the next\n // would-be-allowed log to summarise drops.\n if (!decision.allowed) {\n if (decision.throttledLine) {\n fn(`${tag} ${decision.throttledLine}`)\n this.logRing.push(packageName, `${decision.throttledLine}`)\n }\n return\n }\n const formatted = `${tag} console.${method}`\n fn(formatted, safeArgs)\n // Stringify into the ring buffer for crash-report attachment.\n let line: string\n try {\n line = `${method}: ${JSON.stringify(safeArgs)}`\n } catch {\n line = `${method}: <unserializable>`\n }\n this.logRing.push(packageName, line)\n // Forward to the `mentra-miniapp dev` sidecar (if one is connected\n // for this package). Source=\"background\" so the CLI tags lines as\n // [MentraJS]. The bridge silently drops when no dev server is up,\n // which makes this safe to call unconditionally in prod.\n const argsForBridge = Array.isArray(safeArgs) ? (safeArgs as unknown[]) : [safeArgs]\n devServerBridge.forwardLog(packageName, method, argsForBridge, Date.now(), \"background\")\n return\n }\n\n // 3) Error / unhandled rejection frames — surface as warnings AND\n // drive the crash state machine when one is attached. The\n // controller's outcome may schedule a respawn after a backoff;\n // if it doesn't (CRASHLOOP_DISABLED), we leave the JSContext\n // dead and let the host surface a \"tap to retry\" banner.\n if (iface === \"__error\") {\n const payload = this.tryParseArgs(msg.argsJson)\n // ready_nack is a liveness probe, not a crash. Logging it at error\n // painted WHIP-start queue delay as a Mentra-Call exception.\n if (method === \"ready_nack\") {\n this.logger.warn(`[${packageName}] ${method}`, payload)\n } else {\n this.logger.error(`[${packageName}] ${method}`, payload)\n }\n if (this.crashController) {\n // Only treat \"exception\" + \"unhandledRejection\" + \"uncaught\" as\n // crash signals. `console.error` calls also flow through the\n // log path — those don't end the JSContext.\n const isCrash = method === \"exception\" || method === \"unhandledRejection\" || method === \"uncaught\"\n if (isCrash) {\n this.handleCrash(packageName, this.summarizeReason(payload, method))\n }\n }\n return\n }\n\n // 4) Anything else is a `forwardToRn` from the dispatcher — a\n // handler the native dispatcher didn't know about. We don't have\n // inline handlers for any of these; the runtime's existing path\n // treats them as legacy WebView envelopes by falling through.\n // Log so unknown ifaces surface in dev.\n this.logger.log(`unhandled iface=${iface} method=${method} from ${packageName}`)\n }\n\n /**\n * The wire format for `__bridge.send` is `argsJson = JSON.stringify([raw])`\n * — a single-element array containing the original raw envelope. The\n * dispatcher passes the parsed array through as either:\n * - `msg.args` when the dispatcher unpacked (Android path that turns\n * `argsJson` into a List on the way to RN), or\n * - `msg.argsJson` (iOS path that ships the string verbatim).\n * Cover both shapes here.\n */\n /**\n * Parse a raw bridge envelope just enough to peek at the inner\n * payload's `type` field. Used by the UI_SEND interception path.\n * Returns null if the envelope isn't a valid bridge frame.\n */\n private peekBridgePayloadType(raw: string): {type: string; [k: string]: unknown} | null {\n try {\n const env = JSON.parse(raw) as {payload?: {type?: string}}\n if (env && typeof env.payload === \"object\" && env.payload && typeof env.payload.type === \"string\") {\n return env.payload as {type: string; [k: string]: unknown}\n }\n } catch {\n return null\n }\n return null\n }\n\n private coerceArgsToBridgeRaw(msg: OutboundMessagePayload): string | null {\n if (Array.isArray(msg.args)) {\n const v = msg.args[0]\n return typeof v === \"string\" ? v : null\n }\n if (typeof msg.argsJson === \"string\") {\n try {\n const parsed = JSON.parse(msg.argsJson) as unknown\n if (Array.isArray(parsed) && typeof parsed[0] === \"string\") {\n return parsed[0]\n }\n } catch {\n // fall through\n }\n }\n return null\n }\n\n private tryParseArgs(argsJson: string | undefined): unknown {\n if (!argsJson) return null\n try {\n return JSON.parse(argsJson)\n } catch {\n return argsJson\n }\n }\n\n /**\n * Push a `kind=\"bridge\"` envelope into the named JSContext's\n * `globalThis.__deliver`. Used both by the per-app `sendMessage`\n * registered via {@link registerApp} and by ad-hoc callers (e.g.\n * cloud-relayed stream-status responses route through\n * `LocalMiniappRuntime.handleCloudMessage`, which then calls the\n * `app.sendMessage(serialized)` registered above).\n */\n private dispatchBridgeRaw(packageName: string, raw: string): void {\n void this.crust.mentraJsDispatchToJs(packageName, {kind: \"bridge\", raw})\n }\n}\n"]}
1
+ {"version":3,"file":"MentraJSRouter.js","sourceRoot":"","sources":["../../src/services/MentraJSRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview MentraJSRouter — RN-side host that bridges the per-miniapp\n * JS contexts (iOS-JSC and Android-QuickJS via Zipline) into the existing\n * {@link LocalMiniappRuntime} handler set.\n *\n * Rather than re-implement the 33 dispatch arms inline, the router\n * consumes Crust's `mentrajs_message` Expo event and forwards its\n * `__bridge.send(raw)` payloads to {@link LocalMiniappRuntime.handleRawMessage}.\n * The SDK envelope shape (`{type: \"DISPLAY\", ...}` etc.) is unchanged from\n * the WebView path, so every existing handler runs verbatim. What changes\n * is the *transport*:\n *\n * - **Inbound** (background JS → host): `__dispatch(\"__bridge\", \"send\",\n * [rawJsonString])` → `mentrajs_message` event → router →\n * `LocalMiniappRuntime.handleRawMessage(packageName, raw)`.\n * - **Outbound** (host → background JS): every package registered with\n * the router gets a `sendMessage(raw)` fn that calls\n * `Crust.mentraJsDispatchToJs(packageName, {kind: \"bridge\", raw})`.\n * The polyfill's `__deliver` recognises `kind === \"bridge\"` and calls\n * `__mentraDeliverBridgeRaw(raw)`, which routes into the SDK's\n * {@link DispatchTransport.onMessage} handler.\n *\n * Host-side handler bodies (display fan-out, mic state, transcription,\n * navigation, etc.) live untouched in `LocalMiniappRuntime.ts`.\n *\n * Cloud-message routing (`phone_stream_status`, `phone_managed_stream_status`)\n * goes straight through `LocalMiniappRuntime.handleCloudMessage` — nothing\n * for the router to do; the responses arrive inside an envelope whose\n * `sendMessage` was already registered via the same path here.\n *\n * The router also bridges native error / log / unhandled-rejection\n * events (`iface: \"__log\"`, `iface: \"__error\"`) into the standard\n * `console.*` + Sentry breadcrumb pipeline so miniapp telemetry is\n * visible without bringing up a separate sink.\n */\n\nimport type {EventSubscription} from \"expo-modules-core\"\n\nimport devServerBridge from \"./DevServerBridge\"\nimport type localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport type {InstalledMiniappManifest} from \"./LocalMiniappRuntime\"\nimport type {MentraJSCrashController} from \"./MentraJSCrashController\"\nimport {MentraJSLogRingBuffer, MentraJSLogThrottle, redactSecrets} from \"./MentraJSLogPipeline\"\nimport {islandNotifications} from \"./NotificationsEmitter\"\nimport type {MentraUIRouter} from \"./MentraUIRouter\"\nimport {miniappRunningRegistry} from \"./MiniappRunningRegistry\"\n\n/** The runtime's runtime instance type — the singleton exported from\n * LocalMiniappRuntime.ts (the file's `export default` is the instance,\n * not the class).\n */\ntype LocalMiniappRuntime = typeof localMiniappRuntime\n\n/**\n * Minimal subset of the Crust native module the router uses. Keeping the\n * binding loose lets us mock the module in unit tests without bringing\n * the whole Expo module surface along.\n */\nexport interface MentraJSCrustBinding {\n mentraJsDispatchToJs(packageName: string, envelope: Record<string, unknown>): Promise<void> | void\n mentraJsSetManifest(packageName: string, permissions: string[]): Promise<void> | void\n mentraJsLoadPolyfillBundle?: () => string\n mentraJsSpawn?: (packageName: string, polyfill: string, miniappJs: string) => Promise<boolean> | boolean\n mentraJsKill?: (packageName: string) => Promise<void> | void\n mentraJsAlivePackages?: () => string[]\n addListener: (event: string, handler: (payload: Record<string, unknown>) => void) => EventSubscription\n}\n\nexport interface OutboundMessagePayload {\n packageName: string\n iface: string\n method: string\n argsJson?: string\n args?: unknown[]\n reqId?: string\n // Implementation-internal: any extra fields the dispatcher attached on\n // .forwardToRn (e.g. payload metadata) are passed through verbatim.\n [extra: string]: unknown\n}\n\nexport type RouterLogger = {\n log: (message: string, payload?: unknown) => void\n warn: (message: string, payload?: unknown) => void\n error: (message: string, payload?: unknown) => void\n}\n\nconst defaultLogger: RouterLogger = {\n log: (m, p) => console.log(`[MentraJSRouter] ${m}`, p ?? \"\"),\n\n warn: (m, p) => console.warn(`[MentraJSRouter] ${m}`, p ?? \"\"),\n\n error: (m, p) => console.error(`[MentraJSRouter] ${m}`, p ?? \"\"),\n}\n\ninterface SpawnCache {\n miniappJs: string\n permissions: string[]\n installedManifest?: InstalledMiniappManifest\n}\n\nexport class MentraJSRouter {\n private subscription: EventSubscription | null = null\n private readonly registered: Set<string> = new Set()\n /** Cached spawn arguments so the crash controller can respawn after a backoff. */\n private readonly spawnCache: Map<string, SpawnCache> = new Map()\n /** Active respawn timers (so unregister() can cancel a pending respawn). */\n private readonly respawnTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n\n /**\n * Optional crash controller. When wired, every `__error` frame from a\n * JSContext drives the state machine; an Outcome with\n * scheduleRespawnAfterMs queues a setTimeout that re-spawns the\n * miniapp's last-known JS source after the backoff delay.\n */\n crashController: MentraJSCrashController | null = null\n\n /** Hook called when a package transitions to CRASHLOOP_DISABLED. */\n onCrashloop: ((packageName: string, reason: string) => void) | null = null\n /** Hook called on the 2nd-within-window crash (toast UX). */\n onRestartToast: ((packageName: string, reason: string) => void) | null = null\n\n /**\n * Logging plumbing. Every `__log` frame passes through `redactSecrets`\n * (token/password/etc. scrubbed), then the throttle (100/min sustained\n * per pkg, 500 burst), then lands in the ring buffer + the host\n * logger. The ring buffer holds the last 200 lines/pkg so a crash\n * report can attach \"last words\" context.\n *\n * Replace with new instances in tests; production code uses the\n * defaults.\n */\n logThrottle: MentraJSLogThrottle = new MentraJSLogThrottle()\n logRing: MentraJSLogRingBuffer = new MentraJSLogRingBuffer()\n\n /**\n * Optional UI router — when wired, `__bridge.send` envelopes whose\n * inner payload type is `UI_SEND` get routed to the bound WebView\n * instead of through LocalMiniappRuntime. Two-layer miniapps set\n * this; legacy single-bundle WebView miniapps don't and the bridge\n * frames pass through unchanged.\n */\n uiRouter: MentraUIRouter | null = null\n\n constructor(\n private readonly runtime: LocalMiniappRuntime,\n private readonly crust: MentraJSCrustBinding,\n private readonly logger: RouterLogger = defaultLogger,\n ) {}\n\n /**\n * Subscribe to Crust's `mentrajs_message` event. Idempotent — calling\n * start() twice attaches a single listener.\n */\n start(): void {\n if (this.subscription) return\n this.subscription = this.crust.addListener(\"mentrajs_message\", (raw) => {\n try {\n this.handleOutbound(raw as unknown as OutboundMessagePayload)\n } catch (e) {\n this.logger.error(`outbound handler threw`, {error: String(e), raw})\n }\n })\n // A background script unregistered for missing liveness pings is dead\n // weight: its subscriptions are gone (so the mic releases) while the\n // webview keeps rendering stale state. Route it through the same\n // respawn machinery as a crash so it comes back automatically, with\n // the crash controller's backoff + crash-loop protection.\n this.runtime.onLivenessTimeout = (packageName) => {\n this.handleCrash(packageName, \"liveness: missed pings\")\n }\n }\n\n /**\n * Detach the Crust subscription. The router can be restarted later\n * with start(); registrations survive (the runtime's app map is the\n * source of truth).\n */\n stop(): void {\n this.subscription?.remove()\n this.subscription = null\n this.runtime.onLivenessTimeout = null\n }\n\n /**\n * Register a JSContext with the runtime so its handlers can fire\n * `sendMessage(raw)` against the right context. The router builds a\n * `sendMessage` that calls `Crust.mentraJsDispatchToJs(packageName,\n * {kind: \"bridge\", raw})`; the polyfill's `__deliver` then hands the\n * envelope to `DispatchTransport.onMessage` inside the miniapp.\n *\n * Mirrors `MiniappHost.registerRuntime` on the WebView side. Callers\n * (e.g. {@link spawnAndRegister} below, or the host's miniapp launch\n * pipeline) should call this once per spawn.\n */\n registerApp(packageName: string, installedManifest?: InstalledMiniappManifest): void {\n this.runtime.registerApp(\n packageName,\n (raw: string) => {\n this.dispatchBridgeRaw(packageName, raw)\n },\n installedManifest,\n )\n this.registered.add(packageName)\n // JSContext is the source-of-truth for \"miniapp running\". The\n // home tile / tray reads this registry to project the `running`\n // flag — UI WebView open/close is separate.\n miniappRunningRegistry.add(packageName)\n }\n\n /**\n * Convenience: spawn a JSContext via Crust, register the app, and\n * set its declared permissions. Returns true on successful spawn.\n *\n * The host's miniapp launch path\n * (`mobile/src/services/miniapps/launchLocalMiniapp.ts`) calls this\n * once per JSContext mount.\n */\n async spawnAndRegister(\n packageName: string,\n miniappJs: string,\n options?: {permissions?: string[]; installedManifest?: InstalledMiniappManifest},\n ): Promise<boolean> {\n if (!this.crust.mentraJsSpawn) {\n this.logger.warn(\"mentraJsSpawn not available — host binding missing native function\")\n return false\n }\n const polyfill = this.crust.mentraJsLoadPolyfillBundle?.() ?? \"\"\n if (!polyfill) {\n this.logger.warn(`no polyfill bundle loaded for ${packageName}; spawn will likely fail`)\n }\n const ok = await this.crust.mentraJsSpawn(packageName, polyfill, miniappJs)\n if (!ok) {\n this.logger.error(`spawn failed for ${packageName}`)\n return false\n }\n // Two distinct permission gates:\n // 1. Native dispatcher (JSContext → native bridge): `mentraJsSetManifest`\n // checks per-call iface, e.g. __dispatch(\"mic\", \"start\", ...) requires\n // MICROPHONE.\n // 2. LocalMiniappRuntime (SUBSCRIBE envelope): `registerApp(...,\n // installedManifest)` carries the full manifest so SUBSCRIBE's gate\n // can match stream → permission against declared types.\n if (options?.permissions && options.permissions.length > 0) {\n await this.crust.mentraJsSetManifest(packageName, options.permissions)\n }\n this.registerApp(packageName, options?.installedManifest)\n // Cache spawn arguments so the crash controller can respawn the\n // same code after a backoff. permissions + manifest are cached too\n // because the native side resets setManifest on every spawn and\n // LocalMiniappRuntime's installedManifest entry is keyed by\n // connect-time registration.\n this.spawnCache.set(packageName, {\n miniappJs,\n permissions: options?.permissions ?? [],\n installedManifest: options?.installedManifest,\n })\n this.crashController?.onSpawn(packageName)\n\n // Fire the `init` envelope so the polyfill's __deliver handler\n // resolves `__mentraInitCallback(sessionId)`. The SDK's\n // `registerMiniapp(handler)` wires that callback to construct\n // MiniappSession and call the user's handler. Without this\n // dispatch the user's code never runs — `registerMiniapp` just\n // assigns to a global and waits.\n const sessionId = `${packageName}-${Date.now().toString(36)}`\n void this.crust.mentraJsDispatchToJs(packageName, {kind: \"init\", sessionId})\n\n return true\n }\n\n /**\n * Crash-controller integration. Called from the `__error` frame\n * handler. The controller returns an outcome that tells us whether\n * to respawn (with delay) or leave the context dead.\n */\n private handleCrash(packageName: string, reason: string): void {\n // The native context is dead the instant a crash is observed — invalidate\n // its CONNECT handshake NOW so waitForConnect() blocks through the respawn\n // backoff window instead of resolving immediately against the dead context\n // (an action invoked mid-backoff would otherwise be delivered to nothing).\n this.runtime.resetHandshake(packageName)\n const controller = this.crashController\n if (!controller) return\n const cached = this.spawnCache.get(packageName)\n if (!cached) {\n this.logger.warn(`crash for ${packageName} but no cached spawn args — cannot respawn`)\n controller.onCrash(packageName, reason)\n return\n }\n const outcome = controller.onCrash(packageName, reason)\n if (outcome.surfaceCrashloopBanner) {\n this.onCrashloop?.(packageName, reason)\n islandNotifications.emit({kind: \"miniapp_crashloop\", packageName, reason, timestamp: Date.now()})\n return\n }\n if (outcome.showRestartToast) {\n this.onRestartToast?.(packageName, reason)\n }\n if (outcome.scheduleRespawnAfterMs == null) return\n // Cancel any prior pending respawn before scheduling a new one.\n const existing = this.respawnTimers.get(packageName)\n if (existing) clearTimeout(existing)\n const timer = setTimeout(() => {\n this.respawnTimers.delete(packageName)\n void (async () => {\n try {\n await this.crust.mentraJsKill?.(packageName)\n } catch {\n /* native may have already torn down */\n }\n const ok = await this.crust.mentraJsSpawn?.(\n packageName,\n this.crust.mentraJsLoadPolyfillBundle?.() ?? \"\",\n cached.miniappJs,\n )\n if (!ok) {\n this.logger.error(`crash respawn failed for ${packageName}`)\n return\n }\n if (cached.permissions.length > 0) {\n await this.crust.mentraJsSetManifest(packageName, cached.permissions)\n }\n this.registerApp(packageName, cached.installedManifest)\n controller.onSpawn(packageName)\n // Re-fire the init envelope so the respawned context's\n // `registerMiniapp` handler runs again.\n const sessionId = `${packageName}-${Date.now().toString(36)}`\n void this.crust.mentraJsDispatchToJs(packageName, {kind: \"init\", sessionId})\n this.logger.log(`respawned ${packageName} after crash`)\n })()\n }, outcome.scheduleRespawnAfterMs)\n this.respawnTimers.set(packageName, timer)\n }\n\n private summarizeReason(payload: unknown, method: string): string {\n if (payload && typeof payload === \"object\" && \"message\" in (payload as Record<string, unknown>)) {\n return `${method}: ${(payload as {message: string}).message}`\n }\n return method\n }\n\n /**\n * Tear down a JSContext and unregister from the runtime. Synchronous\n * teardown — the spec drops the 50ms WILL_DISCONNECT grace window\n * because there's no transport handshake to flush in the JSC world.\n */\n async unregister(packageName: string): Promise<void> {\n if (!this.registered.has(packageName)) return\n // Cancel any pending crash-respawn before tearing down — we don't\n // want a stale timer to re-spawn a miniapp the user just disabled.\n const pending = this.respawnTimers.get(packageName)\n if (pending) {\n clearTimeout(pending)\n this.respawnTimers.delete(packageName)\n }\n this.spawnCache.delete(packageName)\n this.crashController?.onKill(packageName)\n this.runtime.unregisterApp(packageName)\n this.registered.delete(packageName)\n // JSContext torn down → miniapp is no longer \"running\".\n miniappRunningRegistry.remove(packageName)\n if (this.crust.mentraJsKill) {\n try {\n await this.crust.mentraJsKill(packageName)\n } catch (e) {\n this.logger.warn(`mentraJsKill threw for ${packageName}: ${String(e)}`)\n }\n }\n }\n\n /** List packages the router has registered. */\n registeredPackages(): string[] {\n return Array.from(this.registered)\n }\n\n /**\n * Validate that a reused background JSContext is still responsive when its\n * WebView comes foreground. The runtime owns the ping/timeout mechanics; the\n * router gates this to registered packages and keeps LocalMiniappView from\n * reaching into runtime internals.\n */\n probeForegroundLiveness(packageName: string, reason = \"foreground-open\", timeoutMs?: number): void {\n if (!this.registered.has(packageName)) return\n this.runtime.probeForegroundLiveness(packageName, reason, timeoutMs)\n }\n\n // ----------------------------------------------------------------\n // Outbound message handling\n // ----------------------------------------------------------------\n\n private handleOutbound(msg: OutboundMessagePayload): void {\n const {packageName, iface, method} = msg\n if (!packageName || !iface || !method) {\n this.logger.warn(`bad mentrajs_message — missing fields`, msg)\n return\n }\n\n // 1) Bridge frames: the SDK's DispatchTransport.send(raw) lands here.\n // Forward raw envelope into LocalMiniappRuntime.handleRawMessage\n // so every existing handler arm runs verbatim.\n if (iface === \"__bridge\" && method === \"send\") {\n const raw = this.coerceArgsToBridgeRaw(msg)\n if (raw == null) {\n this.logger.warn(`__bridge.send had no raw payload`, msg)\n return\n }\n // If the payload is a UI_SEND envelope, route it to the bound\n // WebView instead of LocalMiniappRuntime.\n // session.ui.send → DispatchTransport.send → here.\n if (this.uiRouter) {\n const innerPayload = this.peekBridgePayloadType(raw)\n if (innerPayload?.type === \"UI_SEND\") {\n this.uiRouter.routeFromBackground(packageName, innerPayload)\n return\n }\n }\n this.runtime.handleRawMessage(packageName, raw)\n return\n }\n\n // 2) Log frames — console.* rewired through host. We tag with the\n // packageName so Sentry breadcrumbs can be filtered downstream.\n // Pipeline:\n // raw args → redactSecrets → throttle decision → ring buffer\n // → host logger (which surfaces to dev console / Sentry)\n if (iface === \"__log\") {\n const rawArgs = this.tryParseArgs(msg.argsJson)\n const safeArgs = redactSecrets(rawArgs)\n const decision = this.logThrottle.consume(packageName)\n const tag = `[${packageName}]`\n const fn =\n method === \"warn\"\n ? this.logger.warn\n : method === \"error\" || method === \"fatal\"\n ? this.logger.error\n : this.logger.log\n // Emit a synthetic \"[throttled N]\" line in place of the next\n // would-be-allowed log to summarise drops.\n if (!decision.allowed) {\n if (decision.throttledLine) {\n fn(`${tag} ${decision.throttledLine}`)\n this.logRing.push(packageName, `${decision.throttledLine}`)\n }\n return\n }\n const formatted = `${tag} console.${method}`\n fn(formatted, safeArgs)\n // Stringify into the ring buffer for crash-report attachment.\n let line: string\n try {\n line = `${method}: ${JSON.stringify(safeArgs)}`\n } catch {\n line = `${method}: <unserializable>`\n }\n this.logRing.push(packageName, line)\n // Forward to the `mentra-miniapp dev` sidecar (if one is connected\n // for this package). Source=\"background\" so the CLI tags lines as\n // [MentraJS]. The bridge silently drops when no dev server is up,\n // which makes this safe to call unconditionally in prod.\n const argsForBridge = Array.isArray(safeArgs) ? (safeArgs as unknown[]) : [safeArgs]\n devServerBridge.forwardLog(packageName, method, argsForBridge, Date.now(), \"background\")\n return\n }\n\n // 3) Error / unhandled rejection frames — surface as warnings AND\n // drive the crash state machine when one is attached. The\n // controller's outcome may schedule a respawn after a backoff;\n // if it doesn't (CRASHLOOP_DISABLED), we leave the JSContext\n // dead and let the host surface a \"tap to retry\" banner.\n if (iface === \"__error\") {\n const payload = this.tryParseArgs(msg.argsJson)\n // ready_nack is a liveness probe, not a crash. Logging it at error\n // painted WHIP-start queue delay as a Mentra-Call exception.\n if (method === \"ready_nack\") {\n this.logger.warn(`[${packageName}] ${method}`, payload)\n } else {\n this.logger.error(`[${packageName}] ${method}`, payload)\n }\n if (this.crashController) {\n // Only treat \"exception\" + \"unhandledRejection\" + \"uncaught\" as\n // crash signals. `console.error` calls also flow through the\n // log path — those don't end the JSContext.\n const isCrash = method === \"exception\" || method === \"unhandledRejection\" || method === \"uncaught\"\n if (isCrash) {\n this.handleCrash(packageName, this.summarizeReason(payload, method))\n }\n }\n return\n }\n\n // 4) Anything else is a `forwardToRn` from the dispatcher — a\n // handler the native dispatcher didn't know about. We don't have\n // inline handlers for any of these; the runtime's existing path\n // treats them as legacy WebView envelopes by falling through.\n // Log so unknown ifaces surface in dev.\n this.logger.log(`unhandled iface=${iface} method=${method} from ${packageName}`)\n }\n\n /**\n * The wire format for `__bridge.send` is `argsJson = JSON.stringify([raw])`\n * — a single-element array containing the original raw envelope. The\n * dispatcher passes the parsed array through as either:\n * - `msg.args` when the dispatcher unpacked (Android path that turns\n * `argsJson` into a List on the way to RN), or\n * - `msg.argsJson` (iOS path that ships the string verbatim).\n * Cover both shapes here.\n */\n /**\n * Parse a raw bridge envelope just enough to peek at the inner\n * payload's `type` field. Used by the UI_SEND interception path.\n * Returns null if the envelope isn't a valid bridge frame.\n */\n private peekBridgePayloadType(raw: string): {type: string; [k: string]: unknown} | null {\n try {\n const env = JSON.parse(raw) as {payload?: {type?: string}}\n if (env && typeof env.payload === \"object\" && env.payload && typeof env.payload.type === \"string\") {\n return env.payload as {type: string; [k: string]: unknown}\n }\n } catch {\n return null\n }\n return null\n }\n\n private coerceArgsToBridgeRaw(msg: OutboundMessagePayload): string | null {\n if (Array.isArray(msg.args)) {\n const v = msg.args[0]\n return typeof v === \"string\" ? v : null\n }\n if (typeof msg.argsJson === \"string\") {\n try {\n const parsed = JSON.parse(msg.argsJson) as unknown\n if (Array.isArray(parsed) && typeof parsed[0] === \"string\") {\n return parsed[0]\n }\n } catch {\n // fall through\n }\n }\n return null\n }\n\n private tryParseArgs(argsJson: string | undefined): unknown {\n if (!argsJson) return null\n try {\n return JSON.parse(argsJson)\n } catch {\n return argsJson\n }\n }\n\n /**\n * Push a `kind=\"bridge\"` envelope into the named JSContext's\n * `globalThis.__deliver`. Used both by the per-app `sendMessage`\n * registered via {@link registerApp} and by ad-hoc callers (e.g.\n * cloud-relayed stream-status responses route through\n * `LocalMiniappRuntime.handleCloudMessage`, which then calls the\n * `app.sendMessage(serialized)` registered above).\n */\n private dispatchBridgeRaw(packageName: string, raw: string): void {\n void this.crust.mentraJsDispatchToJs(packageName, {kind: \"bridge\", raw})\n }\n}\n"]}
@@ -16,6 +16,14 @@ declare class MicStateCoordinator {
16
16
  private static instance;
17
17
  private localWantsPcm;
18
18
  private localWantsLc3;
19
+ /**
20
+ * A live ACS call taking the wearer's voice off the glasses over BLE LC3.
21
+ *
22
+ * Tracked separately from the miniapp requirement because the two have independent lifetimes:
23
+ * the call miniapp does not subscribe to `audio_chunk`, and a captions miniapp that stops mid-call
24
+ * must not take the call's microphone with it.
25
+ */
26
+ private callWantsPcm;
19
27
  private configuredVad;
20
28
  private configuredLoudnessGate;
21
29
  private readonly miniappVadOverrides;
@@ -31,6 +39,20 @@ declare class MicStateCoordinator {
31
39
  pcm: boolean;
32
40
  lc3: boolean;
33
41
  } & ConfiguredMicGates): void;
42
+ /**
43
+ * Claim or release raw PCM on behalf of an active call.
44
+ *
45
+ * Called by AcsMeetingService around a call whose uplink is the glasses microphone over BLE LC3.
46
+ * Releasing is a claim release, not a mic shutdown: if a captions miniapp still wants PCM the
47
+ * microphone stays on, which is the whole reason this is a separate flag rather than a setter on
48
+ * the local requirement.
49
+ */
50
+ setCallRequirement(pcm: boolean): void;
51
+ /**
52
+ * Whether anything on this device needs a continuous raw-PCM timeline. Also the condition that
53
+ * forces hardware VAD off: a gate that drops silence turns a call into clipped half-words.
54
+ */
55
+ private get wantsRawPcm();
34
56
  /**
35
57
  * Apply a miniapp-owned gate override without changing the OS preference.
36
58
  * Overrides are lifecycle-scoped and last-live-owner-wins independently for
@@ -1 +1 @@
1
- {"version":3,"file":"MicStateCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/MicStateCoordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,KAAK,OAAO,GAAG,KAAK,GAAG,cAAc,CAAA;AAOrC,UAAU,kBAAkB;IAC1B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IAC3B,mBAAmB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACrC;AAkBD,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmC;IAG1D,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,sBAAsB,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkC;IACtE,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAAkC;IAC/E,OAAO,CAAC,gBAAgB,CAAI;IAE5B,OAAO;WAEO,WAAW,IAAI,mBAAmB;IAOhD;;;OAGG;IACI,oBAAoB,CAAC,GAAG,EAAE;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAC,GAAG,kBAAkB,GAAG,IAAI;IAQzF;;;;OAIG;IACU,sBAAsB,CACjC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,OAAO,EAChB,UAAU,GAAE,kBAAuB,GAClC,OAAO,CAAC,IAAI,CAAC;IAoBhB;;;OAGG;IACI,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAM9D,sEAAsE;IACzD,uBAAuB,CAAC,UAAU,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxF;;;;;OAKG;IACI,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAaxF;;;;OAIG;IACI,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAO3F,OAAO,CAAC,2BAA2B;IAiBnC;;;OAGG;IACH,OAAO,CAAC,UAAU;IAoBlB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACI,KAAK,IAAI,IAAI;IAMb,OAAO,IAAI,IAAI;CAOvB;AAED,QAAA,MAAM,mBAAmB,qBAAoC,CAAA;AAC7D,eAAe,mBAAmB,CAAA"}
1
+ {"version":3,"file":"MicStateCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/MicStateCoordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,KAAK,OAAO,GAAG,KAAK,GAAG,cAAc,CAAA;AAOrC,UAAU,kBAAkB;IAC1B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IAC3B,mBAAmB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACrC;AAkBD,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmC;IAG1D,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,aAAa,CAAQ;IAC7B;;;;;;OAMG;IACH,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,sBAAsB,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkC;IACtE,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAAkC;IAC/E,OAAO,CAAC,gBAAgB,CAAI;IAE5B,OAAO;WAEO,WAAW,IAAI,mBAAmB;IAOhD;;;OAGG;IACI,oBAAoB,CAAC,GAAG,EAAE;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAC,GAAG,kBAAkB,GAAG,IAAI;IAQzF;;;;;;;OAOG;IACI,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAO7C;;;OAGG;IACH,OAAO,KAAK,WAAW,GAEtB;IAED;;;;OAIG;IACU,sBAAsB,CACjC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,OAAO,EAChB,UAAU,GAAE,kBAAuB,GAClC,OAAO,CAAC,IAAI,CAAC;IAoBhB;;;OAGG;IACI,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAM9D,sEAAsE;IACzD,uBAAuB,CAAC,UAAU,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxF;;;;;OAKG;IACI,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAaxF;;;;OAIG;IACI,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAO3F,OAAO,CAAC,2BAA2B;IAiBnC;;;OAGG;IACH,OAAO,CAAC,UAAU;IAoBlB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACI,KAAK,IAAI,IAAI;IAOb,OAAO,IAAI,IAAI;CAOvB;AAED,QAAA,MAAM,mBAAmB,qBAAoC,CAAA;AAC7D,eAAe,mBAAmB,CAAA"}
@@ -31,6 +31,14 @@ class MicStateCoordinator {
31
31
  // Local miniapp requirements (set when miniapps subscribe to audio streams)
32
32
  localWantsPcm = false;
33
33
  localWantsLc3 = false;
34
+ /**
35
+ * A live ACS call taking the wearer's voice off the glasses over BLE LC3.
36
+ *
37
+ * Tracked separately from the miniapp requirement because the two have independent lifetimes:
38
+ * the call miniapp does not subscribe to `audio_chunk`, and a captions miniapp that stops mid-call
39
+ * must not take the call's microphone with it.
40
+ */
41
+ callWantsPcm = false;
34
42
  configuredVad;
35
43
  configuredLoudnessGate;
36
44
  miniappVadOverrides = new Map();
@@ -54,6 +62,28 @@ class MicStateCoordinator {
54
62
  console.log(`${LOG_TAG}: local requirements updated — pcm=${req.pcm} lc3=${req.lc3}`);
55
63
  this.applyUnion();
56
64
  }
65
+ /**
66
+ * Claim or release raw PCM on behalf of an active call.
67
+ *
68
+ * Called by AcsMeetingService around a call whose uplink is the glasses microphone over BLE LC3.
69
+ * Releasing is a claim release, not a mic shutdown: if a captions miniapp still wants PCM the
70
+ * microphone stays on, which is the whole reason this is a separate flag rather than a setter on
71
+ * the local requirement.
72
+ */
73
+ setCallRequirement(pcm) {
74
+ if (this.callWantsPcm === pcm)
75
+ return;
76
+ this.callWantsPcm = pcm;
77
+ console.log(`${LOG_TAG}: call requirement updated — pcm=${pcm}`);
78
+ this.applyUnion();
79
+ }
80
+ /**
81
+ * Whether anything on this device needs a continuous raw-PCM timeline. Also the condition that
82
+ * forces hardware VAD off: a gate that drops silence turns a call into clipped half-words.
83
+ */
84
+ get wantsRawPcm() {
85
+ return this.localWantsPcm || this.callWantsPcm;
86
+ }
57
87
  /**
58
88
  * Apply a miniapp-owned gate override without changing the OS preference.
59
89
  * Overrides are lifecycle-scoped and last-live-owner-wins independently for
@@ -127,7 +157,7 @@ class MicStateCoordinator {
127
157
  const runtimeSettings = { ...settings };
128
158
  const vadOverride = this.latestOverride(this.miniappVadOverrides);
129
159
  const loudnessOverride = this.latestOverride(this.miniappLoudnessGateOverrides);
130
- if (this.localWantsPcm) {
160
+ if (this.wantsRawPcm) {
131
161
  runtimeSettings.voice_activity_detection_enabled = false;
132
162
  }
133
163
  else if (vadOverride) {
@@ -143,7 +173,7 @@ class MicStateCoordinator {
143
173
  * on-device PCM consumers; cloud audio uses LC3 through AudioCloudUplink.
144
174
  */
145
175
  applyUnion() {
146
- const shouldSendPcm = this.localWantsPcm;
176
+ const shouldSendPcm = this.wantsRawPcm;
147
177
  const shouldSendLc3 = this.localWantsLc3;
148
178
  // console.log(
149
179
  // `${LOG_TAG}: applying requirements — pcm=${shouldSendPcm} lc3=${shouldSendLc3}`,
@@ -185,7 +215,7 @@ class MicStateCoordinator {
185
215
  const loudnessOverride = this.latestOverride(this.miniappLoudnessGateOverrides);
186
216
  // Hardware VAD suppresses silence. Raw-audio consumers need a continuous
187
217
  // timeline, so their requirement wins over both OS and miniapp VAD values.
188
- if (this.localWantsPcm)
218
+ if (this.wantsRawPcm)
189
219
  patch.voice_activity_detection_enabled = false;
190
220
  else if (vadOverride)
191
221
  patch.voice_activity_detection_enabled = vadOverride.enabled;
@@ -204,6 +234,7 @@ class MicStateCoordinator {
204
234
  reset() {
205
235
  this.localWantsPcm = false;
206
236
  this.localWantsLc3 = false;
237
+ this.callWantsPcm = false;
207
238
  this.applyUnion();
208
239
  }
209
240
  cleanup() {