@blario/react-native 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +401 -0
  3. package/dist/BlarProvider.d.ts +74 -0
  4. package/dist/actions/batched.d.ts +1 -0
  5. package/dist/actions/click.d.ts +4 -0
  6. package/dist/actions/commit-watch.d.ts +15 -0
  7. package/dist/actions/events.d.ts +35 -0
  8. package/dist/actions/executor.d.ts +29 -0
  9. package/dist/actions/highlight.d.ts +4 -0
  10. package/dist/actions/interaction-guards.d.ts +9 -0
  11. package/dist/actions/landing.d.ts +11 -0
  12. package/dist/actions/navigation-driver.d.ts +22 -0
  13. package/dist/actions/parse.d.ts +4 -0
  14. package/dist/actions/result.d.ts +4 -0
  15. package/dist/actions/scrolling.d.ts +12 -0
  16. package/dist/actions/services.d.ts +20 -0
  17. package/dist/actions/settle.d.ts +13 -0
  18. package/dist/actions/switch.d.ts +3 -0
  19. package/dist/actions/target.d.ts +25 -0
  20. package/dist/actions/text-input.d.ts +9 -0
  21. package/dist/actions/types.d.ts +59 -0
  22. package/dist/agent/batch-commands.d.ts +17 -0
  23. package/dist/agent/compose.d.ts +23 -0
  24. package/dist/agent/controller.d.ts +28 -0
  25. package/dist/agent/frame-runner.d.ts +9 -0
  26. package/dist/agent/frames.d.ts +180 -0
  27. package/dist/agent/handlers/agent-command.d.ts +5 -0
  28. package/dist/agent/handlers/batch.d.ts +5 -0
  29. package/dist/agent/handlers/consent-screenshot.d.ts +4 -0
  30. package/dist/agent/handlers/context.d.ts +16 -0
  31. package/dist/agent/handlers/dom-state.d.ts +4 -0
  32. package/dist/agent/handlers/element-context.d.ts +2 -0
  33. package/dist/agent/handlers/screenshot.d.ts +3 -0
  34. package/dist/agent/handlers/text-dom.d.ts +5 -0
  35. package/dist/agent/stage.d.ts +13 -0
  36. package/dist/agent-mode/action-description.d.ts +3 -0
  37. package/dist/agent-mode/bubbles.d.ts +10 -0
  38. package/dist/agent-mode/colors.d.ts +12 -0
  39. package/dist/agent-mode/controller.d.ts +92 -0
  40. package/dist/agent-mode/cursor-follow.d.ts +12 -0
  41. package/dist/agent-mode/cursor.d.ts +37 -0
  42. package/dist/agent-mode/frames.d.ts +36 -0
  43. package/dist/agent-mode/haptics.d.ts +6 -0
  44. package/dist/agent-mode/keep-awake.d.ts +19 -0
  45. package/dist/agent-mode/layer-stacking.d.ts +7 -0
  46. package/dist/agent-mode/markdown.d.ts +19 -0
  47. package/dist/agent-mode/panel-geometry.d.ts +6 -0
  48. package/dist/agent-mode/resume.d.ts +13 -0
  49. package/dist/agent-mode/run-memory.d.ts +19 -0
  50. package/dist/agent-mode/spotlight-controller.d.ts +68 -0
  51. package/dist/agent-mode/spotlight.d.ts +6 -0
  52. package/dist/agent-mode/state.d.ts +9 -0
  53. package/dist/agent-mode/status.d.ts +11 -0
  54. package/dist/agent-mode/timers.d.ts +6 -0
  55. package/dist/agent-mode/ui/AgentDock.d.ts +37 -0
  56. package/dist/agent-mode/ui/AgentLayer.d.ts +19 -0
  57. package/dist/agent-mode/ui/AgentLayerHost.d.ts +8 -0
  58. package/dist/agent-mode/ui/AgentPanel.d.ts +19 -0
  59. package/dist/agent-mode/ui/AgentTap.d.ts +10 -0
  60. package/dist/agent-mode/ui/MarkdownText.d.ts +8 -0
  61. package/dist/agent-mode/ui/MessageBubbles.d.ts +9 -0
  62. package/dist/agent-mode/ui/PendingOverlay.d.ts +5 -0
  63. package/dist/agent-mode/ui/SpotlightLayer.d.ts +9 -0
  64. package/dist/agent-mode/ui/SpotlightOverlay.d.ts +7 -0
  65. package/dist/agent-mode/ui/StatusCard.d.ts +14 -0
  66. package/dist/agent-mode/ui/TargetPulse.d.ts +9 -0
  67. package/dist/agent-mode/ui/TouchShield.d.ts +5 -0
  68. package/dist/agent-mode/ui/WarningIcon.d.ts +6 -0
  69. package/dist/agent-mode/ui/full-window-overlay.d.ts +5 -0
  70. package/dist/agent-mode/ui/layer-host.d.ts +2 -0
  71. package/dist/backend/api-client.d.ts +38 -0
  72. package/dist/backend/api-types.d.ts +25 -0
  73. package/dist/backend/app-info.d.ts +14 -0
  74. package/dist/backend/backend-transport.d.ts +70 -0
  75. package/dist/backend/backoff.d.ts +9 -0
  76. package/dist/backend/base64.d.ts +1 -0
  77. package/dist/backend/errors.d.ts +17 -0
  78. package/dist/backend/foreground.d.ts +1 -0
  79. package/dist/backend/health.d.ts +17 -0
  80. package/dist/backend/identity-proof.d.ts +23 -0
  81. package/dist/backend/identity.d.ts +27 -0
  82. package/dist/backend/message-metadata.d.ts +19 -0
  83. package/dist/backend/presence.d.ts +20 -0
  84. package/dist/backend/response-cache.d.ts +9 -0
  85. package/dist/backend/session-change.d.ts +2 -0
  86. package/dist/backend/session-socket.d.ts +49 -0
  87. package/dist/backend/session.d.ts +39 -0
  88. package/dist/backend/socket-url.d.ts +9 -0
  89. package/dist/backend/storage.d.ts +13 -0
  90. package/dist/backend/tab-id.d.ts +2 -0
  91. package/dist/blar.d.ts +37 -0
  92. package/dist/chat/ChatLayer.d.ts +29 -0
  93. package/dist/chat/ChatSheet.d.ts +11 -0
  94. package/dist/chat/ChatView.d.ts +19 -0
  95. package/dist/chat/Launcher.d.ts +14 -0
  96. package/dist/chat/bridge.d.ts +22 -0
  97. package/dist/chat/chat-coordinator.d.ts +57 -0
  98. package/dist/chat/chat-state.d.ts +20 -0
  99. package/dist/chat/navigation.d.ts +5 -0
  100. package/dist/chat/options.d.ts +22 -0
  101. package/dist/chat/webview.d.ts +10 -0
  102. package/dist/chat/widget-bundle.generated.d.ts +2 -0
  103. package/dist/chat/widget-shell.d.ts +24 -0
  104. package/dist/confirmation/port.d.ts +12 -0
  105. package/dist/fiber/locate.d.ts +7 -0
  106. package/dist/fiber/narrow.d.ts +5 -0
  107. package/dist/fiber/reader.d.ts +10 -0
  108. package/dist/fiber/text.d.ts +4 -0
  109. package/dist/fiber/types.d.ts +21 -0
  110. package/dist/host/border-radius.d.ts +1 -0
  111. package/dist/host/instance.d.ts +9 -0
  112. package/dist/host/measure.d.ts +3 -0
  113. package/dist/host/rect.d.ts +22 -0
  114. package/dist/host/visible-area.d.ts +2 -0
  115. package/dist/index.d.ts +10 -0
  116. package/dist/nudge/NudgeBubble.d.ts +15 -0
  117. package/dist/nudge/nudge.d.ts +23 -0
  118. package/dist/optional/expo-haptics.d.ts +1 -0
  119. package/dist/optional/expo-keep-awake.d.ts +1 -0
  120. package/dist/optional/react-native-haptic-feedback.d.ts +1 -0
  121. package/dist/optional/react-native-keep-awake.d.ts +1 -0
  122. package/dist/overlay/AgentOverlay.d.ts +5 -0
  123. package/dist/screenshot/capture-backend.d.ts +6 -0
  124. package/dist/screenshot/screenshotter.d.ts +18 -0
  125. package/dist/screenshot/skia.d.ts +35 -0
  126. package/dist/screenshot/view-shot.d.ts +2 -0
  127. package/dist/snapshot/capture.d.ts +36 -0
  128. package/dist/snapshot/classify.d.ts +11 -0
  129. package/dist/snapshot/collect.d.ts +27 -0
  130. package/dist/snapshot/element-context.d.ts +9 -0
  131. package/dist/snapshot/icon.d.ts +2 -0
  132. package/dist/snapshot/kinds.d.ts +3 -0
  133. package/dist/snapshot/modals.d.ts +2 -0
  134. package/dist/snapshot/navigation.d.ts +14 -0
  135. package/dist/snapshot/ordering.d.ts +2 -0
  136. package/dist/snapshot/properties.d.ts +14 -0
  137. package/dist/snapshot/registry.d.ts +16 -0
  138. package/dist/snapshot/render.d.ts +10 -0
  139. package/dist/snapshot/route-summary.d.ts +3 -0
  140. package/dist/snapshot/scroll.d.ts +15 -0
  141. package/dist/snapshot/text-layout.d.ts +8 -0
  142. package/dist/snapshot/view.d.ts +30 -0
  143. package/dist/snapshot/visibility.d.ts +17 -0
  144. package/dist/snapshot/wrappers.d.ts +11 -0
  145. package/dist/telemetry/SdkErrorBoundary.d.ts +15 -0
  146. package/dist/telemetry/build-info.generated.d.ts +2 -0
  147. package/dist/telemetry/envelope.d.ts +53 -0
  148. package/dist/telemetry/reporter.d.ts +33 -0
  149. package/dist/telemetry/scrub.d.ts +1 -0
  150. package/dist/telemetry/sdk-reporter.d.ts +5 -0
  151. package/dist/transport/connection-options.d.ts +18 -0
  152. package/dist/transport/relay-transport.d.ts +17 -0
  153. package/dist/transport/transport.d.ts +7 -0
  154. package/dist/ui/insets.d.ts +8 -0
  155. package/dist/ui/strings.d.ts +33 -0
  156. package/dist/ui/useInsets.d.ts +2 -0
  157. package/dist/version.d.ts +1 -0
  158. package/package.json +87 -3
  159. package/src/BlarProvider.tsx +462 -0
  160. package/src/actions/batched.ts +16 -0
  161. package/src/actions/click.ts +54 -0
  162. package/src/actions/commit-watch.ts +86 -0
  163. package/src/actions/events.ts +58 -0
  164. package/src/actions/executor.ts +147 -0
  165. package/src/actions/highlight.ts +13 -0
  166. package/src/actions/interaction-guards.ts +44 -0
  167. package/src/actions/landing.ts +26 -0
  168. package/src/actions/navigation-driver.ts +131 -0
  169. package/src/actions/parse.ts +82 -0
  170. package/src/actions/result.ts +14 -0
  171. package/src/actions/scrolling.ts +112 -0
  172. package/src/actions/services.ts +24 -0
  173. package/src/actions/settle.ts +40 -0
  174. package/src/actions/switch.ts +31 -0
  175. package/src/actions/target.ts +77 -0
  176. package/src/actions/text-input.ts +82 -0
  177. package/src/actions/types.ts +52 -0
  178. package/src/agent/batch-commands.ts +33 -0
  179. package/src/agent/compose.ts +67 -0
  180. package/src/agent/controller.ts +106 -0
  181. package/src/agent/frame-runner.ts +27 -0
  182. package/src/agent/frames.ts +193 -0
  183. package/src/agent/handlers/agent-command.ts +74 -0
  184. package/src/agent/handlers/batch.ts +72 -0
  185. package/src/agent/handlers/consent-screenshot.ts +44 -0
  186. package/src/agent/handlers/context.ts +17 -0
  187. package/src/agent/handlers/dom-state.ts +19 -0
  188. package/src/agent/handlers/element-context.ts +9 -0
  189. package/src/agent/handlers/screenshot.ts +24 -0
  190. package/src/agent/handlers/text-dom.ts +17 -0
  191. package/src/agent/stage.ts +22 -0
  192. package/src/agent-mode/action-description.ts +25 -0
  193. package/src/agent-mode/bubbles.ts +31 -0
  194. package/src/agent-mode/colors.ts +40 -0
  195. package/src/agent-mode/controller.ts +459 -0
  196. package/src/agent-mode/cursor-follow.ts +43 -0
  197. package/src/agent-mode/cursor.ts +118 -0
  198. package/src/agent-mode/frames.ts +70 -0
  199. package/src/agent-mode/haptics.ts +49 -0
  200. package/src/agent-mode/keep-awake.ts +107 -0
  201. package/src/agent-mode/layer-stacking.ts +16 -0
  202. package/src/agent-mode/markdown.ts +84 -0
  203. package/src/agent-mode/panel-geometry.ts +27 -0
  204. package/src/agent-mode/resume.ts +16 -0
  205. package/src/agent-mode/run-memory.ts +65 -0
  206. package/src/agent-mode/spotlight-controller.ts +255 -0
  207. package/src/agent-mode/spotlight.ts +14 -0
  208. package/src/agent-mode/state.ts +36 -0
  209. package/src/agent-mode/status.ts +13 -0
  210. package/src/agent-mode/timers.ts +11 -0
  211. package/src/agent-mode/ui/AgentDock.tsx +285 -0
  212. package/src/agent-mode/ui/AgentLayer.tsx +123 -0
  213. package/src/agent-mode/ui/AgentLayerHost.tsx +37 -0
  214. package/src/agent-mode/ui/AgentPanel.tsx +187 -0
  215. package/src/agent-mode/ui/AgentTap.tsx +190 -0
  216. package/src/agent-mode/ui/MarkdownText.tsx +55 -0
  217. package/src/agent-mode/ui/MessageBubbles.tsx +58 -0
  218. package/src/agent-mode/ui/PendingOverlay.tsx +32 -0
  219. package/src/agent-mode/ui/SpotlightLayer.tsx +60 -0
  220. package/src/agent-mode/ui/SpotlightOverlay.tsx +35 -0
  221. package/src/agent-mode/ui/StatusCard.tsx +137 -0
  222. package/src/agent-mode/ui/TargetPulse.tsx +74 -0
  223. package/src/agent-mode/ui/TouchShield.tsx +22 -0
  224. package/src/agent-mode/ui/WarningIcon.tsx +32 -0
  225. package/src/agent-mode/ui/full-window-overlay.ts +16 -0
  226. package/src/agent-mode/ui/layer-host.ts +11 -0
  227. package/src/backend/api-client.ts +210 -0
  228. package/src/backend/api-types.ts +56 -0
  229. package/src/backend/app-info.ts +40 -0
  230. package/src/backend/backend-transport.ts +338 -0
  231. package/src/backend/backoff.ts +16 -0
  232. package/src/backend/base64.ts +25 -0
  233. package/src/backend/errors.ts +32 -0
  234. package/src/backend/foreground.ts +10 -0
  235. package/src/backend/health.ts +64 -0
  236. package/src/backend/identity-proof.ts +151 -0
  237. package/src/backend/identity.ts +31 -0
  238. package/src/backend/message-metadata.ts +28 -0
  239. package/src/backend/presence.ts +95 -0
  240. package/src/backend/response-cache.ts +45 -0
  241. package/src/backend/session-change.ts +14 -0
  242. package/src/backend/session-socket.ts +292 -0
  243. package/src/backend/session.ts +188 -0
  244. package/src/backend/socket-url.ts +25 -0
  245. package/src/backend/storage.ts +49 -0
  246. package/src/backend/tab-id.ts +24 -0
  247. package/src/blar.ts +100 -0
  248. package/src/chat/ChatLayer.tsx +109 -0
  249. package/src/chat/ChatSheet.tsx +84 -0
  250. package/src/chat/ChatView.tsx +133 -0
  251. package/src/chat/Launcher.tsx +72 -0
  252. package/src/chat/bridge.ts +56 -0
  253. package/src/chat/chat-coordinator.ts +189 -0
  254. package/src/chat/chat-state.ts +35 -0
  255. package/src/chat/navigation.ts +15 -0
  256. package/src/chat/options.ts +39 -0
  257. package/src/chat/webview.ts +23 -0
  258. package/src/chat/widget-bundle.generated.ts +2 -0
  259. package/src/chat/widget-shell.ts +170 -0
  260. package/src/confirmation/port.ts +15 -0
  261. package/src/fiber/locate.ts +41 -0
  262. package/src/fiber/narrow.ts +15 -0
  263. package/src/fiber/reader.ts +98 -0
  264. package/src/fiber/text.ts +52 -0
  265. package/src/fiber/types.ts +24 -0
  266. package/src/host/border-radius.ts +11 -0
  267. package/src/host/instance.ts +30 -0
  268. package/src/host/measure.ts +25 -0
  269. package/src/host/rect.ts +45 -0
  270. package/src/host/visible-area.ts +5 -0
  271. package/src/index.ts +10 -0
  272. package/src/nudge/NudgeBubble.tsx +117 -0
  273. package/src/nudge/nudge.ts +100 -0
  274. package/src/optional/expo-haptics.ts +11 -0
  275. package/src/optional/expo-keep-awake.ts +11 -0
  276. package/src/optional/react-native-haptic-feedback.ts +11 -0
  277. package/src/optional/react-native-keep-awake.ts +11 -0
  278. package/src/overlay/AgentOverlay.tsx +16 -0
  279. package/src/screenshot/capture-backend.ts +7 -0
  280. package/src/screenshot/screenshotter.ts +39 -0
  281. package/src/screenshot/skia.ts +107 -0
  282. package/src/screenshot/view-shot.ts +41 -0
  283. package/src/snapshot/capture.ts +163 -0
  284. package/src/snapshot/classify.ts +40 -0
  285. package/src/snapshot/collect.ts +136 -0
  286. package/src/snapshot/element-context.ts +66 -0
  287. package/src/snapshot/icon.ts +21 -0
  288. package/src/snapshot/kinds.ts +10 -0
  289. package/src/snapshot/modals.ts +21 -0
  290. package/src/snapshot/navigation.ts +47 -0
  291. package/src/snapshot/ordering.ts +26 -0
  292. package/src/snapshot/properties.ts +71 -0
  293. package/src/snapshot/registry.ts +33 -0
  294. package/src/snapshot/render.ts +140 -0
  295. package/src/snapshot/route-summary.ts +15 -0
  296. package/src/snapshot/scroll.ts +90 -0
  297. package/src/snapshot/text-layout.ts +59 -0
  298. package/src/snapshot/view.ts +87 -0
  299. package/src/snapshot/visibility.ts +63 -0
  300. package/src/snapshot/wrappers.ts +69 -0
  301. package/src/telemetry/SdkErrorBoundary.tsx +28 -0
  302. package/src/telemetry/build-info.generated.ts +2 -0
  303. package/src/telemetry/envelope.ts +83 -0
  304. package/src/telemetry/reporter.ts +170 -0
  305. package/src/telemetry/scrub.ts +10 -0
  306. package/src/telemetry/sdk-reporter.ts +37 -0
  307. package/src/transport/connection-options.ts +65 -0
  308. package/src/transport/relay-transport.ts +82 -0
  309. package/src/transport/transport.ts +8 -0
  310. package/src/ui/insets.ts +31 -0
  311. package/src/ui/strings.ts +102 -0
  312. package/src/ui/useInsets.ts +10 -0
  313. package/src/version.ts +1 -0
@@ -0,0 +1,49 @@
1
+ import { isFunction, isRecord } from '../fiber/narrow.ts';
2
+ import { EXPO_HAPTICS } from '../optional/expo-haptics.ts';
3
+ import { REACT_NATIVE_HAPTIC_FEEDBACK } from '../optional/react-native-haptic-feedback.ts';
4
+
5
+ export interface HapticsAdapter {
6
+ name: string;
7
+ warn: () => void;
8
+ }
9
+
10
+ function hapticFeedbackAdapter(): HapticsAdapter | null {
11
+ const loaded = REACT_NATIVE_HAPTIC_FEEDBACK;
12
+ const module = isRecord(loaded) && isRecord(loaded.default) ? loaded.default : loaded;
13
+ if (!isRecord(module) || !isFunction(module.trigger)) {
14
+ return null;
15
+ }
16
+ const trigger = module.trigger;
17
+ return {
18
+ name: 'react-native-haptic-feedback',
19
+ warn: () => {
20
+ trigger('notificationWarning', { enableVibrateFallback: false, ignoreAndroidSystemSettings: false });
21
+ },
22
+ };
23
+ }
24
+
25
+ function expoHapticsAdapter(): HapticsAdapter | null {
26
+ const module = EXPO_HAPTICS;
27
+ if (!isRecord(module) || !isFunction(module.notificationAsync)) {
28
+ return null;
29
+ }
30
+ const notify = module.notificationAsync;
31
+ const types = module.NotificationFeedbackType;
32
+ const warning = isRecord(types) && typeof types.Warning === 'string' ? types.Warning : 'warning';
33
+ return {
34
+ name: 'expo-haptics',
35
+ warn: () => {
36
+ Promise.resolve(notify(warning)).catch(() => undefined);
37
+ },
38
+ };
39
+ }
40
+
41
+ export const HAPTICS_ADAPTER: HapticsAdapter | null = hapticFeedbackAdapter() ?? expoHapticsAdapter();
42
+
43
+ export function warnByHaptics(adapter: HapticsAdapter | null): void {
44
+ try {
45
+ adapter?.warn();
46
+ } catch {
47
+ return;
48
+ }
49
+ }
@@ -0,0 +1,107 @@
1
+ import { isFunction, isRecord } from '../fiber/narrow.ts';
2
+ import { EXPO_KEEP_AWAKE } from '../optional/expo-keep-awake.ts';
3
+ import { REACT_NATIVE_KEEP_AWAKE } from '../optional/react-native-keep-awake.ts';
4
+
5
+ export type KeepAwakeHolder = 'ai-turn' | 'agent-run';
6
+
7
+ export interface KeepAwakeAdapter {
8
+ name: string;
9
+ activate: () => void;
10
+ deactivate: () => void;
11
+ }
12
+
13
+ const EXPO_TAG = 'blar-agent';
14
+
15
+ function expoAdapter(): KeepAwakeAdapter | null {
16
+ const module = EXPO_KEEP_AWAKE;
17
+ if (!isRecord(module) || !isFunction(module.activateKeepAwakeAsync) || !isFunction(module.deactivateKeepAwake)) {
18
+ return null;
19
+ }
20
+ const activate = module.activateKeepAwakeAsync;
21
+ const deactivate = module.deactivateKeepAwake;
22
+ return {
23
+ name: 'expo-keep-awake',
24
+ activate: () => {
25
+ Promise.resolve(activate(EXPO_TAG)).catch(() => undefined);
26
+ },
27
+ deactivate: () => {
28
+ Promise.resolve(deactivate(EXPO_TAG)).catch(() => undefined);
29
+ },
30
+ };
31
+ }
32
+
33
+ interface KeepAwakeMethods {
34
+ activate?: unknown;
35
+ deactivate?: unknown;
36
+ }
37
+
38
+ function communityAdapter(): KeepAwakeAdapter | null {
39
+ const module = REACT_NATIVE_KEEP_AWAKE;
40
+ const candidate = isRecord(module) && module.default !== undefined ? module.default : module;
41
+ if (!isRecord(candidate) && !isFunction(candidate)) {
42
+ return null;
43
+ }
44
+ const { activate, deactivate } = candidate as KeepAwakeMethods;
45
+ if (!isFunction(activate) || !isFunction(deactivate)) {
46
+ return null;
47
+ }
48
+ return { name: 'react-native-keep-awake', activate: () => activate(), deactivate: () => deactivate() };
49
+ }
50
+
51
+ export const KEEP_AWAKE_ADAPTER: KeepAwakeAdapter | null = expoAdapter() ?? communityAdapter();
52
+
53
+ export class KeepAwake {
54
+ private readonly holders = new Set<KeepAwakeHolder>();
55
+
56
+ private reportedMissing = false;
57
+
58
+ constructor(
59
+ private readonly adapter: KeepAwakeAdapter | null,
60
+ private readonly log?: (message: string) => void,
61
+ ) {}
62
+
63
+ hold(holder: KeepAwakeHolder): void {
64
+ if (this.holders.has(holder)) {
65
+ return;
66
+ }
67
+ this.holders.add(holder);
68
+ if (this.holders.size > 1) {
69
+ return;
70
+ }
71
+ if (this.adapter === null) {
72
+ this.reportMissing();
73
+ return;
74
+ }
75
+ this.invoke('activate');
76
+ }
77
+
78
+ release(holder: KeepAwakeHolder): void {
79
+ if (!this.holders.delete(holder) || this.holders.size > 0 || this.adapter === null) {
80
+ return;
81
+ }
82
+ this.invoke('deactivate');
83
+ }
84
+
85
+ releaseAll(): void {
86
+ const held = this.holders.size > 0;
87
+ this.holders.clear();
88
+ if (held && this.adapter !== null) {
89
+ this.invoke('deactivate');
90
+ }
91
+ }
92
+
93
+ private invoke(method: 'activate' | 'deactivate'): void {
94
+ try {
95
+ this.adapter?.[method]();
96
+ } catch {
97
+ this.reportMissing();
98
+ }
99
+ }
100
+
101
+ private reportMissing(): void {
102
+ if (!this.reportedMissing) {
103
+ this.reportedMissing = true;
104
+ this.log?.('keep-awake unavailable: install expo-keep-awake or react-native-keep-awake to keep the screen on during a run');
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,16 @@
1
+ export class LayerStacking {
2
+ private openModals = 0;
3
+
4
+ constructor(private readonly countOpenModals: () => number) {}
5
+
6
+ baseline(): void {
7
+ this.openModals = this.countOpenModals();
8
+ }
9
+
10
+ coveredByNewModal(): boolean {
11
+ const current = this.countOpenModals();
12
+ const covered = current > this.openModals;
13
+ this.openModals = current;
14
+ return covered;
15
+ }
16
+ }
@@ -0,0 +1,84 @@
1
+ export type InlineKind = 'text' | 'bold' | 'italic' | 'code' | 'link';
2
+
3
+ export interface Inline {
4
+ kind: InlineKind;
5
+ text: string;
6
+ }
7
+
8
+ export type MarkdownBlock =
9
+ | { kind: 'paragraph'; inlines: Inline[] }
10
+ | { kind: 'bullet'; inlines: Inline[] }
11
+ | { kind: 'numbered'; marker: string; inlines: Inline[] };
12
+
13
+ const INLINE_PATTERN = /(\*\*[^*]+\*\*|__[^_]+__|`[^`]+`|\[[^\]]+\]\([^)\s]+\)|\*[^*\s][^*]*\*|_[^_\s][^_]*_)/g;
14
+ const BULLET = /^\s*[-*+]\s+(.*)$/;
15
+ const NUMBERED = /^\s*(\d+)[.)]\s+(.*)$/;
16
+ const HEADING = /^\s*#{1,6}\s+(.*)$/;
17
+
18
+ function toInline(token: string): Inline {
19
+ if (token.startsWith('**') || token.startsWith('__')) {
20
+ return { kind: 'bold', text: token.slice(2, -2) };
21
+ }
22
+ if (token.startsWith('`')) {
23
+ return { kind: 'code', text: token.slice(1, -1) };
24
+ }
25
+ if (token.startsWith('[')) {
26
+ return { kind: 'link', text: token.slice(1, token.indexOf('](')) };
27
+ }
28
+ return { kind: 'italic', text: token.slice(1, -1) };
29
+ }
30
+
31
+ export function parseInlines(source: string): Inline[] {
32
+ const inlines: Inline[] = [];
33
+ let last = 0;
34
+ for (const match of source.matchAll(INLINE_PATTERN)) {
35
+ const index = match.index ?? 0;
36
+ if (index > last) {
37
+ inlines.push({ kind: 'text', text: source.slice(last, index) });
38
+ }
39
+ inlines.push(toInline(match[0]));
40
+ last = index + match[0].length;
41
+ }
42
+ if (last < source.length) {
43
+ inlines.push({ kind: 'text', text: source.slice(last) });
44
+ }
45
+ return inlines;
46
+ }
47
+
48
+ export function parseMarkdown(source: string): MarkdownBlock[] {
49
+ const blocks: MarkdownBlock[] = [];
50
+ let paragraph: string[] = [];
51
+ const flush = (): void => {
52
+ if (paragraph.length > 0) {
53
+ blocks.push({ kind: 'paragraph', inlines: parseInlines(paragraph.join(' ')) });
54
+ paragraph = [];
55
+ }
56
+ };
57
+ for (const line of source.split(/\r?\n/)) {
58
+ const heading = HEADING.exec(line);
59
+ const bullet = BULLET.exec(line);
60
+ const numbered = NUMBERED.exec(line);
61
+ if (line.trim() === '') {
62
+ flush();
63
+ } else if (heading !== null) {
64
+ flush();
65
+ blocks.push({ kind: 'paragraph', inlines: [{ kind: 'bold', text: heading[1] ?? '' }] });
66
+ } else if (bullet !== null) {
67
+ flush();
68
+ blocks.push({ kind: 'bullet', inlines: parseInlines(bullet[1] ?? '') });
69
+ } else if (numbered !== null) {
70
+ flush();
71
+ blocks.push({ kind: 'numbered', marker: `${numbered[1] ?? ''}.`, inlines: parseInlines(numbered[2] ?? '') });
72
+ } else {
73
+ paragraph.push(line.trim());
74
+ }
75
+ }
76
+ flush();
77
+ return blocks;
78
+ }
79
+
80
+ export function plainText(source: string): string {
81
+ return parseMarkdown(source)
82
+ .map((block) => block.inlines.map((inline) => inline.text).join(''))
83
+ .join(' ');
84
+ }
@@ -0,0 +1,27 @@
1
+ import type { Band, Rect } from '../host/rect.ts';
2
+
3
+ export type PanelDock = 'bottom' | 'top';
4
+
5
+ export const STACK_GAP = 12;
6
+
7
+ export function freeBand(safeTop: number, stackTop: number, keyboardTop: number): Band {
8
+ return { top: safeTop + STACK_GAP, bottom: Math.min(stackTop, keyboardTop) - STACK_GAP };
9
+ }
10
+
11
+ export function messageCap(room: number): number {
12
+ return (Math.max(0, room) * 2) / 7;
13
+ }
14
+
15
+ function overlapArea(first: Rect, second: Rect): number {
16
+ const width = Math.min(first.x + first.width, second.x + second.width) - Math.max(first.x, second.x);
17
+ const height = Math.min(first.y + first.height, second.y + second.height) - Math.max(first.y, second.y);
18
+ return width > 0 && height > 0 ? width * height : 0;
19
+ }
20
+
21
+ export function chooseDock(target: Rect, bottomStack: Rect, topStack: Rect): PanelDock {
22
+ const underBottom = overlapArea(target, bottomStack);
23
+ if (underBottom === 0) {
24
+ return 'bottom';
25
+ }
26
+ return overlapArea(target, topStack) < underBottom ? 'top' : 'bottom';
27
+ }
@@ -0,0 +1,16 @@
1
+ import type { AgentModePhase } from './state.ts';
2
+
3
+ export interface SavedRun {
4
+ sessionId: number;
5
+ lastAgentFrameAt: number;
6
+ }
7
+
8
+ export const RESUME_MAX_AGE_MS = 120_000;
9
+
10
+ export function isResumable(run: SavedRun, sessionId: number, now: number): boolean {
11
+ return run.sessionId === sessionId && now - run.lastAgentFrameAt <= RESUME_MAX_AGE_MS;
12
+ }
13
+
14
+ export function shouldResumeAgentMode(input: { run: SavedRun | null; sessionId: number; phase: AgentModePhase; now: number }): boolean {
15
+ return input.run !== null && isResumable(input.run, input.sessionId, input.now) && input.phase === 'idle';
16
+ }
@@ -0,0 +1,65 @@
1
+ import { isRecord } from '../fiber/narrow.ts';
2
+ import type { KeyValueStorage } from '../backend/storage.ts';
3
+ import type { SavedRun } from './resume.ts';
4
+
5
+ export interface RunMemory {
6
+ recall: () => SavedRun | null;
7
+ remember: (at: number) => void;
8
+ forget: () => void;
9
+ }
10
+
11
+ export const NO_RUN_MEMORY: RunMemory = {
12
+ recall: () => null,
13
+ remember: () => undefined,
14
+ forget: () => undefined,
15
+ };
16
+
17
+ function parseSavedRun(raw: string | null): SavedRun | null {
18
+ if (raw === null) {
19
+ return null;
20
+ }
21
+ try {
22
+ const parsed: unknown = JSON.parse(raw);
23
+ if (isRecord(parsed) && typeof parsed.sessionId === 'number' && typeof parsed.lastAgentFrameAt === 'number') {
24
+ return { sessionId: parsed.sessionId, lastAgentFrameAt: parsed.lastAgentFrameAt };
25
+ }
26
+ return null;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ export class StoredRunMemory implements RunMemory {
33
+ private saved: SavedRun | null = null;
34
+
35
+ constructor(
36
+ private readonly storage: KeyValueStorage,
37
+ private readonly key: string,
38
+ private readonly getSessionId: () => number | null,
39
+ ) {}
40
+
41
+ async load(): Promise<void> {
42
+ this.saved = parseSavedRun(await this.storage.getItem(this.key).catch(() => null));
43
+ }
44
+
45
+ recall(): SavedRun | null {
46
+ return this.saved;
47
+ }
48
+
49
+ remember(at: number): void {
50
+ const sessionId = this.getSessionId();
51
+ if (sessionId === null) {
52
+ return;
53
+ }
54
+ this.saved = { sessionId, lastAgentFrameAt: at };
55
+ void this.storage.setItem(this.key, JSON.stringify(this.saved)).catch(() => undefined);
56
+ }
57
+
58
+ forget(): void {
59
+ if (this.saved === null) {
60
+ return;
61
+ }
62
+ this.saved = null;
63
+ void this.storage.removeItem(this.key).catch(() => undefined);
64
+ }
65
+ }
@@ -0,0 +1,255 @@
1
+ import type { ReadingRequest } from '../actions/services.ts';
2
+ import type { TargetView } from '../actions/target.ts';
3
+ import type { ConfirmationOutcome, ConfirmationRequest } from '../confirmation/port.ts';
4
+ import type { Band, Rect } from '../host/rect.ts';
5
+ import { readingTimeMs } from './bubbles.ts';
6
+ import { type Hole, holeAround } from './spotlight.ts';
7
+ import type { Timers } from './timers.ts';
8
+
9
+ export type SpotlightSnapshot =
10
+ | { kind: 'none' }
11
+ | { kind: 'reading'; id: number; target: Rect; hole: Hole; text: string | null; durationMs: number; shown: boolean }
12
+ | { kind: 'confirming'; id: number; target: Rect | null; hole: Hole | null; request: ConfirmationRequest; shown: boolean };
13
+
14
+ export interface SpotlightEnvironment {
15
+ timers: Timers;
16
+ isForeground: () => boolean;
17
+ stopRun: () => void;
18
+ alert: () => void;
19
+ }
20
+
21
+ type Listener = (snapshot: SpotlightSnapshot) => void;
22
+
23
+ export const TRACK_MS = 33;
24
+ export const CONFIRMATION_TIMEOUT_MS = 60_000;
25
+ export const REVEAL_MAX_MS = 1_500;
26
+
27
+ const SETTLE_MIN_MS = 150;
28
+ const SETTLE_MAX_MS = 1_200;
29
+ const SETTLE_STABLE_SAMPLES = 3;
30
+
31
+ function sameRect(first: Rect, second: Rect): boolean {
32
+ return first.x === second.x && first.y === second.y && first.width === second.width && first.height === second.height;
33
+ }
34
+
35
+ export class SpotlightController {
36
+ private snapshot: SpotlightSnapshot = { kind: 'none' };
37
+
38
+ private readonly listeners = new Set<Listener>();
39
+
40
+ private finishReading: ((completed: boolean) => void) | null = null;
41
+
42
+ private readingTimer: unknown = null;
43
+
44
+ private finishConfirmation: ((outcome: ConfirmationOutcome) => void) | null = null;
45
+
46
+ private confirmationTimer: unknown = null;
47
+
48
+ private trackTimer: unknown = null;
49
+
50
+ private revealTimer: unknown = null;
51
+
52
+ private view: TargetView | null = null;
53
+
54
+ private placing: number | null = null;
55
+
56
+ private shown = 0;
57
+
58
+ constructor(private readonly environment: SpotlightEnvironment) {}
59
+
60
+ getSnapshot(): SpotlightSnapshot {
61
+ return this.snapshot;
62
+ }
63
+
64
+ subscribe(listener: Listener): () => void {
65
+ this.listeners.add(listener);
66
+ return () => this.listeners.delete(listener);
67
+ }
68
+
69
+ async read(request: ReadingRequest): Promise<boolean> {
70
+ this.endReading(true);
71
+ this.endConfirmation('declined');
72
+ if (!this.environment.isForeground()) {
73
+ return true;
74
+ }
75
+ const target = await request.measure();
76
+ if (target === null) {
77
+ return true;
78
+ }
79
+ this.shown += 1;
80
+ const id = this.shown;
81
+ const durationMs = readingTimeMs(request.text ?? '');
82
+ return new Promise((resolve) => {
83
+ this.finishReading = resolve;
84
+ this.publish({ kind: 'reading', id, target, hole: holeAround(target, request.radius), text: request.text, durationMs, shown: false });
85
+ this.awaitPlacement(id, request);
86
+ });
87
+ }
88
+
89
+ async confirm(request: ConfirmationRequest): Promise<ConfirmationOutcome> {
90
+ this.endReading(true);
91
+ this.endConfirmation('declined');
92
+ const target = request.target === null ? null : await request.target.measure();
93
+ this.shown += 1;
94
+ const id = this.shown;
95
+ return new Promise((resolve) => {
96
+ this.finishConfirmation = resolve;
97
+ const hole = target === null || request.target === null ? null : holeAround(target, request.target.radius);
98
+ this.publish({ kind: 'confirming', id, target, hole, request, shown: target === null });
99
+ this.confirmationTimer = this.environment.timers.set(() => this.endConfirmation('timed_out'), CONFIRMATION_TIMEOUT_MS);
100
+ if (request.target !== null) {
101
+ this.awaitPlacement(id, request.target);
102
+ }
103
+ this.environment.alert();
104
+ });
105
+ }
106
+
107
+ async place(id: number, band: Band): Promise<void> {
108
+ const current = this.snapshot;
109
+ if (current.kind === 'none' || current.id !== id || current.shown || this.placing === id) {
110
+ return;
111
+ }
112
+ this.placing = id;
113
+ const reveal = this.view?.reveal;
114
+ if (reveal !== undefined) {
115
+ await reveal(band);
116
+ await this.settle(id);
117
+ }
118
+ this.show(id);
119
+ }
120
+
121
+ approve(): void {
122
+ this.endConfirmation('confirmed');
123
+ }
124
+
125
+ cancel(): void {
126
+ if (this.finishConfirmation === null) {
127
+ return;
128
+ }
129
+ this.environment.stopRun();
130
+ this.endConfirmation('declined');
131
+ }
132
+
133
+ skip(): void {
134
+ this.endReading(true);
135
+ }
136
+
137
+ backgrounded(): void {
138
+ this.endReading(true);
139
+ }
140
+
141
+ clear(): void {
142
+ this.endReading(false);
143
+ this.endConfirmation('declined');
144
+ }
145
+
146
+ private awaitPlacement(id: number, view: TargetView): void {
147
+ this.view = view;
148
+ this.placing = null;
149
+ this.revealTimer = this.environment.timers.set(() => this.show(id), REVEAL_MAX_MS);
150
+ this.track(id, view);
151
+ }
152
+
153
+ private show(id: number): void {
154
+ const current = this.snapshot;
155
+ if (current.kind === 'none' || current.id !== id || current.shown) {
156
+ return;
157
+ }
158
+ this.clearTimer(this.revealTimer);
159
+ this.revealTimer = null;
160
+ this.publish({ ...current, shown: true });
161
+ if (current.kind === 'reading') {
162
+ this.readingTimer = this.environment.timers.set(() => this.endReading(true), current.durationMs);
163
+ }
164
+ }
165
+
166
+ private settle(id: number): Promise<void> {
167
+ const { timers } = this.environment;
168
+ const startedAt = timers.now();
169
+ return new Promise((resolve) => {
170
+ let last: Rect | null = null;
171
+ let stable = 0;
172
+ const sample = (): void => {
173
+ const current = this.snapshot;
174
+ if (current.kind === 'none' || current.id !== id) {
175
+ resolve();
176
+ return;
177
+ }
178
+ if (current.target !== null && last !== null && sameRect(current.target, last)) {
179
+ stable += 1;
180
+ } else {
181
+ stable = 0;
182
+ last = current.target;
183
+ }
184
+ const elapsed = timers.now() - startedAt;
185
+ if ((stable >= SETTLE_STABLE_SAMPLES && elapsed >= SETTLE_MIN_MS) || elapsed >= SETTLE_MAX_MS) {
186
+ resolve();
187
+ return;
188
+ }
189
+ timers.set(sample, TRACK_MS);
190
+ };
191
+ timers.set(sample, TRACK_MS);
192
+ });
193
+ }
194
+
195
+ private endConfirmation(outcome: ConfirmationOutcome): void {
196
+ const finish = this.finishConfirmation;
197
+ if (finish === null) {
198
+ return;
199
+ }
200
+ this.finishConfirmation = null;
201
+ this.clearTimer(this.confirmationTimer);
202
+ this.confirmationTimer = null;
203
+ this.stopTracking();
204
+ this.publish({ kind: 'none' });
205
+ finish(outcome);
206
+ }
207
+
208
+ private endReading(completed: boolean): void {
209
+ const finish = this.finishReading;
210
+ if (finish === null) {
211
+ return;
212
+ }
213
+ this.finishReading = null;
214
+ this.clearTimer(this.readingTimer);
215
+ this.readingTimer = null;
216
+ this.stopTracking();
217
+ this.publish({ kind: 'none' });
218
+ finish(completed);
219
+ }
220
+
221
+ private track(id: number, target: TargetView): void {
222
+ this.trackTimer = this.environment.timers.set(() => {
223
+ void target.measure().then((rect) => {
224
+ const current = this.snapshot;
225
+ if (current.kind === 'none' || current.id !== id) {
226
+ return;
227
+ }
228
+ if (rect !== null && (current.target === null || !sameRect(rect, current.target))) {
229
+ this.publish({ ...current, target: rect, hole: holeAround(rect, target.radius) });
230
+ }
231
+ this.track(id, target);
232
+ });
233
+ }, TRACK_MS);
234
+ }
235
+
236
+ private stopTracking(): void {
237
+ this.clearTimer(this.trackTimer);
238
+ this.trackTimer = null;
239
+ this.clearTimer(this.revealTimer);
240
+ this.revealTimer = null;
241
+ this.view = null;
242
+ this.placing = null;
243
+ }
244
+
245
+ private clearTimer(handle: unknown): void {
246
+ if (handle !== null) {
247
+ this.environment.timers.clear(handle);
248
+ }
249
+ }
250
+
251
+ private publish(next: SpotlightSnapshot): void {
252
+ this.snapshot = next;
253
+ this.listeners.forEach((listener) => listener(next));
254
+ }
255
+ }
@@ -0,0 +1,14 @@
1
+ import type { Rect } from '../host/rect.ts';
2
+
3
+ export interface Hole extends Rect {
4
+ radius: number;
5
+ }
6
+
7
+ export const HOLE_PADDING = 8;
8
+
9
+ export function holeAround(target: Rect, elementRadius: number): Hole {
10
+ const width = target.width + HOLE_PADDING * 2;
11
+ const height = target.height + HOLE_PADDING * 2;
12
+ const radius = Math.max(0, Math.min(elementRadius, width / 2, height / 2)) + HOLE_PADDING / 2;
13
+ return { x: target.x - HOLE_PADDING, y: target.y - HOLE_PADDING, width, height, radius };
14
+ }
@@ -0,0 +1,36 @@
1
+ export type AgentModePhase = 'idle' | 'pending' | 'active' | 'exiting';
2
+
3
+ const VALID_TRANSITIONS: Readonly<Record<AgentModePhase, readonly AgentModePhase[]>> = {
4
+ idle: ['pending', 'active'],
5
+ pending: ['active', 'idle'],
6
+ active: ['exiting'],
7
+ exiting: ['idle'],
8
+ };
9
+
10
+ export class AgentModeStateMachine {
11
+ private phase: AgentModePhase = 'idle';
12
+
13
+ getPhase(): AgentModePhase {
14
+ return this.phase;
15
+ }
16
+
17
+ isActive(): boolean {
18
+ return this.phase === 'active';
19
+ }
20
+
21
+ isEngaged(): boolean {
22
+ return this.phase === 'active' || this.phase === 'exiting';
23
+ }
24
+
25
+ transition(to: AgentModePhase): boolean {
26
+ if (this.phase === to || !VALID_TRANSITIONS[this.phase].includes(to)) {
27
+ return false;
28
+ }
29
+ this.phase = to;
30
+ return true;
31
+ }
32
+
33
+ forceIdle(): void {
34
+ this.phase = 'idle';
35
+ }
36
+ }
@@ -0,0 +1,13 @@
1
+ export type AgentStatus = { kind: 'thinking' } | { kind: 'executing' } | { kind: 'text'; text: string };
2
+
3
+ export const THINKING: AgentStatus = { kind: 'thinking' };
4
+ export const EXECUTING: AgentStatus = { kind: 'executing' };
5
+
6
+ const TRAILING_ELLIPSIS = /(\.\.\.|…)$/;
7
+
8
+ export function isStepMessage(message: string, progressType: string | null): boolean {
9
+ if (progressType !== null && progressType.length > 0) {
10
+ return progressType === 'step';
11
+ }
12
+ return !TRAILING_ELLIPSIS.test(message.trim());
13
+ }