@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,459 @@
1
+ import type { AgentCommandData } from '../actions/types.ts';
2
+ import type { Admission, AgentStage } from '../agent/stage.ts';
3
+ import { type AgentBubble, isShowing, keepOnly, markLeaving, readingTimeMs, without } from './bubbles.ts';
4
+ import { type AgentModeEvent, isBubbleRole } from './frames.ts';
5
+ import type { KeepAwake } from './keep-awake.ts';
6
+ import { isResumable, shouldResumeAgentMode } from './resume.ts';
7
+ import { NO_RUN_MEMORY, type RunMemory } from './run-memory.ts';
8
+ import { type AgentModePhase, AgentModeStateMachine } from './state.ts';
9
+ import { type AgentStatus, EXECUTING, isStepMessage, THINKING } from './status.ts';
10
+ import { SYSTEM_TIMERS, type Timers } from './timers.ts';
11
+
12
+ export type CancelReason = 'user_requested' | 'user_cancelled';
13
+
14
+ export type ResumeDecision = 'resumed' | 'discarded' | 'kept' | 'none';
15
+
16
+ export interface QueuedChip {
17
+ message: string | null;
18
+ }
19
+
20
+ export interface AgentModeSnapshot {
21
+ phase: AgentModePhase;
22
+ status: AgentStatus;
23
+ bubbles: AgentBubble[];
24
+ queued: QueuedChip | null;
25
+ }
26
+
27
+ export interface AgentModeEffects {
28
+ sendCancel: (reason: CancelReason) => void;
29
+ onActive: () => void;
30
+ onIdle: () => void;
31
+ }
32
+
33
+ interface Waiting {
34
+ admission: Admission;
35
+ resolve: (admitted: boolean) => void;
36
+ }
37
+
38
+ type Listener = (snapshot: AgentModeSnapshot) => void;
39
+
40
+ export const PENDING_MS = 2_000;
41
+ export const RUN_IDLE_TIMEOUT_MS = 120_000;
42
+ export const QUEUED_CHIP_MS = 5_000;
43
+ export const BUBBLE_EXIT_MS = 300;
44
+ export const HANDOFF_WAIT_MS = 30_000;
45
+
46
+ const TERMINAL_STATUSES: readonly string[] = ['waiting', 'cancelled', 'idle'];
47
+
48
+ export class AgentModeController implements AgentStage {
49
+ private readonly machine = new AgentModeStateMachine();
50
+
51
+ private readonly listeners = new Set<Listener>();
52
+
53
+ private readonly bubbleTimers = new Map<string, unknown[]>();
54
+
55
+ private waiting: Waiting[] = [];
56
+
57
+ private status: AgentStatus = THINKING;
58
+
59
+ private bubbles: AgentBubble[] = [];
60
+
61
+ private queued: QueuedChip | null = null;
62
+
63
+ private runGeneration = 0;
64
+
65
+ private cancelledRun: number | null = null;
66
+
67
+ private bubbleCounter = 0;
68
+
69
+ private pendingTimer: unknown = null;
70
+
71
+ private watchdog: unknown = null;
72
+
73
+ private queuedTimer: unknown = null;
74
+
75
+ private handoffWatchdog: unknown = null;
76
+
77
+ private absorbCompletion = false;
78
+
79
+ private snapshot: AgentModeSnapshot;
80
+
81
+ constructor(
82
+ private readonly effects: AgentModeEffects,
83
+ private readonly keepAwake: KeepAwake,
84
+ private readonly memory: RunMemory = NO_RUN_MEMORY,
85
+ private readonly timers: Timers = SYSTEM_TIMERS,
86
+ ) {
87
+ this.snapshot = this.buildSnapshot();
88
+ }
89
+
90
+ getSnapshot(): AgentModeSnapshot {
91
+ return this.snapshot;
92
+ }
93
+
94
+ subscribe(listener: Listener): () => void {
95
+ this.listeners.add(listener);
96
+ return () => this.listeners.delete(listener);
97
+ }
98
+
99
+ connecting(sessionId: number): ResumeDecision {
100
+ const run = this.memory.recall();
101
+ const now = this.timers.now();
102
+ if (shouldResumeAgentMode({ run, sessionId, phase: this.machine.getPhase(), now })) {
103
+ this.enterActive();
104
+ this.beginHandoffWait();
105
+ return 'resumed';
106
+ }
107
+ if (run !== null && !isResumable(run, sessionId, now)) {
108
+ this.memory.forget();
109
+ return 'discarded';
110
+ }
111
+ return run === null ? 'none' : 'kept';
112
+ }
113
+
114
+ received(): void {
115
+ this.rememberRun();
116
+ }
117
+
118
+ admit(admission: Admission): Promise<boolean> {
119
+ this.noteActivity();
120
+ this.noteHandoffActivity();
121
+ if (this.machine.isActive()) {
122
+ this.openRun(admission);
123
+ this.publish();
124
+ return Promise.resolve(true);
125
+ }
126
+ return new Promise((resolve) => {
127
+ this.waiting.push({ admission, resolve });
128
+ if (this.machine.getPhase() === 'idle') {
129
+ this.enterPending();
130
+ }
131
+ });
132
+ }
133
+
134
+ beforeAction(command: AgentCommandData): void {
135
+ if (!this.machine.isActive() || command.action.type === 'highlight') {
136
+ return;
137
+ }
138
+ if (command.teaching_explanation !== null) {
139
+ this.showBubble(command.teaching_explanation);
140
+ }
141
+ this.status = EXECUTING;
142
+ this.publish();
143
+ }
144
+
145
+ settled(): void {
146
+ if (this.machine.isActive()) {
147
+ this.status = THINKING;
148
+ this.publish();
149
+ }
150
+ }
151
+
152
+ isCancelled(): boolean {
153
+ return this.cancelledRun !== null && this.cancelledRun === this.runGeneration;
154
+ }
155
+
156
+ stop(): void {
157
+ const phase = this.machine.getPhase();
158
+ if (phase === 'idle') {
159
+ return;
160
+ }
161
+ this.effects.sendCancel(phase === 'pending' ? 'user_cancelled' : 'user_requested');
162
+ this.latch();
163
+ this.exit();
164
+ }
165
+
166
+ endClosedRun(): void {
167
+ this.memory.forget();
168
+ if (this.machine.getPhase() !== 'idle') {
169
+ this.latch();
170
+ this.exit();
171
+ }
172
+ }
173
+
174
+ socketFailed(): void {
175
+ this.exit();
176
+ }
177
+
178
+ handleEvent(event: AgentModeEvent): void {
179
+ switch (event.kind) {
180
+ case 'progress':
181
+ this.noteActivity();
182
+ this.showProgress(event.message, event.progressType);
183
+ return;
184
+ case 'message':
185
+ this.noteActivity();
186
+ this.showMessage(event.role, event.text, event.waitForResponse);
187
+ return;
188
+ case 'queued':
189
+ this.showQueued(event.message);
190
+ return;
191
+ case 'execution_complete':
192
+ case 'acknowledged':
193
+ if (this.absorbCompletion) {
194
+ this.absorbCompletion = false;
195
+ this.armHandoffWatchdog();
196
+ return;
197
+ }
198
+ this.latch();
199
+ this.exit();
200
+ return;
201
+ case 'cancelled':
202
+ case 'session_closed':
203
+ this.latch();
204
+ this.exit();
205
+ return;
206
+ case 'history':
207
+ this.applyHistory(event.agentModeActive, event.agentStatus);
208
+ return;
209
+ case 'thinking':
210
+ this.noteActivity();
211
+ this.keepAwake.hold('ai-turn');
212
+ return;
213
+ case 'thinking_stop':
214
+ this.keepAwake.release('ai-turn');
215
+ return;
216
+ case 'session_started':
217
+ return;
218
+ }
219
+ }
220
+
221
+ dispose(): void {
222
+ this.clearTimer(this.pendingTimer);
223
+ this.clearTimer(this.watchdog);
224
+ this.clearTimer(this.queuedTimer);
225
+ this.clearTimer(this.handoffWatchdog);
226
+ this.bubbleTimers.forEach((handles) => handles.forEach((handle) => this.timers.clear(handle)));
227
+ this.bubbleTimers.clear();
228
+ this.waiting.splice(0).forEach((entry) => entry.resolve(false));
229
+ this.keepAwake.releaseAll();
230
+ this.machine.forceIdle();
231
+ this.listeners.clear();
232
+ }
233
+
234
+ private enterPending(): void {
235
+ if (!this.machine.transition('pending')) {
236
+ return;
237
+ }
238
+ this.pendingTimer = this.timers.set(() => this.finishPending(), PENDING_MS);
239
+ this.publish();
240
+ }
241
+
242
+ private finishPending(): void {
243
+ this.pendingTimer = null;
244
+ this.enterActive();
245
+ const admitted = this.waiting.splice(0);
246
+ admitted.forEach((entry) => this.openRun(entry.admission));
247
+ this.publish();
248
+ admitted.forEach((entry) => entry.resolve(true));
249
+ }
250
+
251
+ private enterActive(): void {
252
+ if (!this.machine.transition('active')) {
253
+ return;
254
+ }
255
+ this.cancelledRun = null;
256
+ this.status = THINKING;
257
+ this.keepAwake.hold('agent-run');
258
+ this.rememberRun();
259
+ this.armWatchdog();
260
+ this.effects.onActive();
261
+ this.publish();
262
+ }
263
+
264
+ private applyHistory(agentModeActive: boolean, agentStatus: string | null): void {
265
+ if (agentStatus !== null && TERMINAL_STATUSES.includes(agentStatus)) {
266
+ this.exit();
267
+ return;
268
+ }
269
+ if (agentModeActive && this.machine.getPhase() === 'idle') {
270
+ this.enterActive();
271
+ }
272
+ }
273
+
274
+ private openRun(admission: Admission): void {
275
+ this.runGeneration += 1;
276
+ this.replaceBubbles(keepOnly(this.bubbles, admission.kind === 'command' ? admission.keepText : null));
277
+ }
278
+
279
+ private latch(): void {
280
+ this.cancelledRun = this.runGeneration;
281
+ }
282
+
283
+ private exit(): void {
284
+ this.memory.forget();
285
+ const phase = this.machine.getPhase();
286
+ if (phase === 'pending') {
287
+ this.leavePending();
288
+ return;
289
+ }
290
+ this.teardown();
291
+ if (!this.machine.transition('exiting')) {
292
+ return;
293
+ }
294
+ this.publish();
295
+ this.machine.transition('idle');
296
+ this.status = THINKING;
297
+ this.replaceBubbles([]);
298
+ this.queued = null;
299
+ this.publish();
300
+ this.waiting.splice(0).forEach((entry) => entry.resolve(false));
301
+ this.effects.onIdle();
302
+ }
303
+
304
+ private leavePending(): void {
305
+ this.clearTimer(this.pendingTimer);
306
+ this.pendingTimer = null;
307
+ this.machine.transition('idle');
308
+ this.waiting.splice(0).forEach((entry) => entry.resolve(false));
309
+ this.publish();
310
+ }
311
+
312
+ private teardown(): void {
313
+ this.clearTimer(this.watchdog);
314
+ this.watchdog = null;
315
+ this.clearTimer(this.queuedTimer);
316
+ this.queuedTimer = null;
317
+ this.clearHandoffWait();
318
+ this.keepAwake.release('agent-run');
319
+ this.keepAwake.release('ai-turn');
320
+ }
321
+
322
+ private showProgress(message: string, progressType: string | null): void {
323
+ if (!this.machine.isActive() || message.length === 0) {
324
+ return;
325
+ }
326
+ if (isStepMessage(message, progressType)) {
327
+ this.showBubble(message);
328
+ } else {
329
+ this.status = { kind: 'text', text: message };
330
+ }
331
+ this.publish();
332
+ }
333
+
334
+ private showMessage(role: string, text: string, waitForResponse: boolean): void {
335
+ if (!this.machine.isActive()) {
336
+ return;
337
+ }
338
+ if (isBubbleRole(role) && text.length > 0) {
339
+ this.showBubble(text);
340
+ this.publish();
341
+ }
342
+ if (waitForResponse) {
343
+ this.exit();
344
+ }
345
+ }
346
+
347
+ private showQueued(message: string | null): void {
348
+ if (!this.machine.isActive()) {
349
+ return;
350
+ }
351
+ this.clearTimer(this.queuedTimer);
352
+ this.queued = { message };
353
+ this.queuedTimer = this.timers.set(() => {
354
+ this.queuedTimer = null;
355
+ this.queued = null;
356
+ this.publish();
357
+ }, QUEUED_CHIP_MS);
358
+ this.publish();
359
+ }
360
+
361
+ private showBubble(text: string): void {
362
+ if (isShowing(this.bubbles, text)) {
363
+ return;
364
+ }
365
+ this.bubbleCounter += 1;
366
+ const id = `bubble-${this.bubbleCounter}`;
367
+ this.bubbles = [...this.bubbles, { id, text, leaving: false }];
368
+ const leave = this.timers.set(() => {
369
+ this.bubbles = markLeaving(this.bubbles, id);
370
+ this.publish();
371
+ const remove = this.timers.set(() => {
372
+ this.bubbleTimers.delete(id);
373
+ this.bubbles = without(this.bubbles, id);
374
+ this.publish();
375
+ }, BUBBLE_EXIT_MS);
376
+ this.bubbleTimers.set(id, [remove]);
377
+ }, readingTimeMs(text));
378
+ this.bubbleTimers.set(id, [leave]);
379
+ }
380
+
381
+ private replaceBubbles(next: AgentBubble[]): void {
382
+ const kept = new Set(next.map((bubble) => bubble.id));
383
+ for (const [id, handles] of this.bubbleTimers) {
384
+ if (!kept.has(id)) {
385
+ handles.forEach((handle) => this.timers.clear(handle));
386
+ this.bubbleTimers.delete(id);
387
+ }
388
+ }
389
+ this.bubbles = next;
390
+ }
391
+
392
+ private noteActivity(): void {
393
+ if (this.machine.isEngaged()) {
394
+ this.armWatchdog();
395
+ this.rememberRun();
396
+ }
397
+ }
398
+
399
+ private rememberRun(): void {
400
+ if (this.machine.isEngaged()) {
401
+ this.memory.remember(this.timers.now());
402
+ }
403
+ }
404
+
405
+ private beginHandoffWait(): void {
406
+ this.clearHandoffWait();
407
+ this.absorbCompletion = true;
408
+ this.armHandoffWatchdog();
409
+ }
410
+
411
+ private armHandoffWatchdog(): void {
412
+ this.clearTimer(this.handoffWatchdog);
413
+ this.handoffWatchdog = this.timers.set(() => {
414
+ this.handoffWatchdog = null;
415
+ this.absorbCompletion = false;
416
+ if (this.machine.isEngaged()) {
417
+ this.exit();
418
+ }
419
+ }, HANDOFF_WAIT_MS);
420
+ }
421
+
422
+ private noteHandoffActivity(): void {
423
+ if (this.absorbCompletion) {
424
+ this.armHandoffWatchdog();
425
+ } else if (this.handoffWatchdog !== null) {
426
+ this.clearHandoffWait();
427
+ }
428
+ }
429
+
430
+ private clearHandoffWait(): void {
431
+ this.absorbCompletion = false;
432
+ this.clearTimer(this.handoffWatchdog);
433
+ this.handoffWatchdog = null;
434
+ }
435
+
436
+ private armWatchdog(): void {
437
+ this.clearTimer(this.watchdog);
438
+ this.watchdog = this.timers.set(() => {
439
+ this.watchdog = null;
440
+ this.latch();
441
+ this.exit();
442
+ }, RUN_IDLE_TIMEOUT_MS);
443
+ }
444
+
445
+ private clearTimer(handle: unknown): void {
446
+ if (handle !== null) {
447
+ this.timers.clear(handle);
448
+ }
449
+ }
450
+
451
+ private publish(): void {
452
+ this.snapshot = this.buildSnapshot();
453
+ this.listeners.forEach((listener) => listener(this.snapshot));
454
+ }
455
+
456
+ private buildSnapshot(): AgentModeSnapshot {
457
+ return { phase: this.machine.getPhase(), status: this.status, bubbles: this.bubbles, queued: this.queued };
458
+ }
459
+ }
@@ -0,0 +1,43 @@
1
+ import type { Clock } from '../actions/settle.ts';
2
+ import type { Rect } from '../host/rect.ts';
3
+
4
+ export const FOLLOW_MIN_MS = 350;
5
+ export const FOLLOW_MAX_MS = 1400;
6
+ export const FOLLOW_POLL_MS = 33;
7
+
8
+ const SETTLE_STABLE_SAMPLES = 2;
9
+
10
+ export interface Follow {
11
+ measure: () => Promise<Rect | null>;
12
+ place: (rect: Rect) => void;
13
+ isLive: () => boolean;
14
+ clock: Clock;
15
+ }
16
+
17
+ function sameRect(first: Rect | null, second: Rect): boolean {
18
+ return first !== null && first.x === second.x && first.y === second.y && first.width === second.width && first.height === second.height;
19
+ }
20
+
21
+ export async function followUntilLanded({ measure, place, isLive, clock }: Follow): Promise<void> {
22
+ const startedAt = clock.now();
23
+ let last: Rect | null = null;
24
+ let stable = 0;
25
+ for (;;) {
26
+ const rect = await measure();
27
+ if (!isLive()) {
28
+ return;
29
+ }
30
+ if (rect !== null && !sameRect(last, rect)) {
31
+ last = rect;
32
+ stable = 0;
33
+ place(rect);
34
+ } else {
35
+ stable += 1;
36
+ }
37
+ const elapsed = clock.now() - startedAt;
38
+ if ((stable >= SETTLE_STABLE_SAMPLES && elapsed >= FOLLOW_MIN_MS) || elapsed > FOLLOW_MAX_MS) {
39
+ return;
40
+ }
41
+ await clock.sleep(FOLLOW_POLL_MS);
42
+ }
43
+ }
@@ -0,0 +1,118 @@
1
+ import type { Clock } from '../actions/settle.ts';
2
+ import type { Rect } from '../host/rect.ts';
3
+ import { followUntilLanded } from './cursor-follow.ts';
4
+
5
+ export interface CursorSnapshot {
6
+ tap: number;
7
+ target: Rect | null;
8
+ landed: boolean;
9
+ }
10
+
11
+ export interface CursorEnvironment {
12
+ clock: Clock;
13
+ isForeground: () => boolean;
14
+ }
15
+
16
+ type Listener = (snapshot: CursorSnapshot) => void;
17
+
18
+ export const ATTACH_MAX_MS = 1000;
19
+ export const PRESS_MS = 450;
20
+
21
+ export class CursorController {
22
+ private snapshot: CursorSnapshot = { tap: 0, target: null, landed: false };
23
+
24
+ private readonly listeners = new Set<Listener>();
25
+
26
+ private arrival: Promise<void> = Promise.resolve();
27
+
28
+ private sessionEnded = false;
29
+
30
+ private move = 0;
31
+
32
+ private attached = false;
33
+
34
+ private onAttached: (() => void) | null = null;
35
+
36
+ constructor(private readonly environment: CursorEnvironment) {}
37
+
38
+ getSnapshot(): CursorSnapshot {
39
+ return this.snapshot;
40
+ }
41
+
42
+ subscribe(listener: Listener): () => void {
43
+ this.listeners.add(listener);
44
+ return () => this.listeners.delete(listener);
45
+ }
46
+
47
+ beginSession(): void {
48
+ this.sessionEnded = false;
49
+ }
50
+
51
+ moveTo(measure: () => Promise<Rect | null>): void {
52
+ if (this.sessionEnded) {
53
+ return;
54
+ }
55
+ this.move += 1;
56
+ const move = this.move;
57
+ this.publish({ tap: move, target: null, landed: false });
58
+ this.arrival = this.tap(measure, move, () => !this.sessionEnded && move === this.move);
59
+ }
60
+
61
+ attach(): void {
62
+ this.attached = true;
63
+ this.onAttached?.();
64
+ this.onAttached = null;
65
+ }
66
+
67
+ detach(): void {
68
+ this.attached = false;
69
+ }
70
+
71
+ whenLanded(): Promise<void> {
72
+ return this.arrival;
73
+ }
74
+
75
+ endSession(): void {
76
+ this.sessionEnded = true;
77
+ this.move += 1;
78
+ if (this.snapshot.target !== null) {
79
+ this.publish({ ...this.snapshot, target: null });
80
+ }
81
+ }
82
+
83
+ private async tap(measure: () => Promise<Rect | null>, move: number, isLive: () => boolean): Promise<void> {
84
+ if (!this.environment.isForeground()) {
85
+ return;
86
+ }
87
+ await this.untilAttached();
88
+ if (!isLive()) {
89
+ return;
90
+ }
91
+ const place = (target: Rect): void => this.publish({ tap: move, target, landed: false });
92
+ await followUntilLanded({ measure, place, isLive, clock: this.environment.clock });
93
+ if (!isLive() || this.snapshot.target === null) {
94
+ return;
95
+ }
96
+ this.publish({ ...this.snapshot, landed: true });
97
+ void this.environment.clock.sleep(PRESS_MS).then(() => {
98
+ if (isLive()) {
99
+ this.publish({ ...this.snapshot, target: null });
100
+ }
101
+ });
102
+ }
103
+
104
+ private untilAttached(): Promise<void> {
105
+ if (this.attached) {
106
+ return Promise.resolve();
107
+ }
108
+ const attached = new Promise<void>((resolve) => {
109
+ this.onAttached = resolve;
110
+ });
111
+ return Promise.race([attached, this.environment.clock.sleep(ATTACH_MAX_MS)]);
112
+ }
113
+
114
+ private publish(next: CursorSnapshot): void {
115
+ this.snapshot = next;
116
+ this.listeners.forEach((listener) => listener(next));
117
+ }
118
+ }
@@ -0,0 +1,70 @@
1
+ import { isRecord, nonEmptyString, type UnknownRecord } from '../fiber/narrow.ts';
2
+
3
+ export type AgentModeEvent =
4
+ | { kind: 'progress'; message: string; progressType: string | null }
5
+ | { kind: 'message'; role: string; text: string; waitForResponse: boolean }
6
+ | { kind: 'queued'; message: string | null }
7
+ | { kind: 'execution_complete' }
8
+ | { kind: 'acknowledged' }
9
+ | { kind: 'cancelled' }
10
+ | { kind: 'session_closed' }
11
+ | { kind: 'history'; agentModeActive: boolean; intent: string | null; agentStatus: string | null }
12
+ | { kind: 'thinking' }
13
+ | { kind: 'thinking_stop' }
14
+ | { kind: 'session_started'; sessionId: number };
15
+
16
+ const BUBBLE_ROLES: readonly string[] = ['ai', 'agent', 'system'];
17
+
18
+ function readMessage(message: unknown): AgentModeEvent | null {
19
+ if (!isRecord(message) || message.is_draft === true || message.is_ephemeral === true) {
20
+ return null;
21
+ }
22
+ const role = nonEmptyString(message.role);
23
+ if (role === null) {
24
+ return null;
25
+ }
26
+ const text = nonEmptyString(message.text) ?? nonEmptyString(message.body) ?? '';
27
+ return { kind: 'message', role, text, waitForResponse: message.wait_for_response === true };
28
+ }
29
+
30
+ function readHistory(frame: UnknownRecord): AgentModeEvent {
31
+ return {
32
+ kind: 'history',
33
+ agentModeActive: frame.agent_mode_active === true,
34
+ intent: nonEmptyString(frame.agent_mode_intent),
35
+ agentStatus: nonEmptyString(frame.agent_status),
36
+ };
37
+ }
38
+
39
+ export function readAgentModeEvent(frame: UnknownRecord): AgentModeEvent | null {
40
+ switch (frame.type) {
41
+ case 'ai_progress':
42
+ return { kind: 'progress', message: typeof frame.message === 'string' ? frame.message : '', progressType: nonEmptyString(frame.progress_type) };
43
+ case 'message':
44
+ return readMessage(frame.message);
45
+ case 'message_queued':
46
+ return { kind: 'queued', message: nonEmptyString(frame.message) };
47
+ case 'agent_execution_complete':
48
+ return { kind: 'execution_complete' };
49
+ case 'agent_acknowledged':
50
+ return { kind: 'acknowledged' };
51
+ case 'agent_cancelled':
52
+ return { kind: 'cancelled' };
53
+ case 'session_closed':
54
+ return { kind: 'session_closed' };
55
+ case 'history':
56
+ return readHistory(frame);
57
+ case 'ai_thinking':
58
+ return { kind: 'thinking' };
59
+ case 'ai_thinking_stop':
60
+ return { kind: 'thinking_stop' };
61
+ case 'activation_session_started':
62
+ return typeof frame.session_id === 'number' ? { kind: 'session_started', sessionId: frame.session_id } : null;
63
+ default:
64
+ return null;
65
+ }
66
+ }
67
+
68
+ export function isBubbleRole(role: string): boolean {
69
+ return BUBBLE_ROLES.includes(role);
70
+ }