dextunnel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +211 -0
  2. package/README.md +112 -0
  3. package/SECURITY.md +27 -0
  4. package/SUPPORT.md +43 -0
  5. package/package.json +44 -0
  6. package/public/client-shared.js +1831 -0
  7. package/public/favicon.svg +11 -0
  8. package/public/host.html +29 -0
  9. package/public/host.js +2079 -0
  10. package/public/index.html +28 -0
  11. package/public/index.js +98 -0
  12. package/public/live-bridge-lifecycle.js +258 -0
  13. package/public/live-bridge-retry-state.js +61 -0
  14. package/public/live-selection-intent.js +79 -0
  15. package/public/remote-operator-state.js +316 -0
  16. package/public/remote.html +167 -0
  17. package/public/remote.js +3967 -0
  18. package/public/styles.css +2793 -0
  19. package/public/surface-view-state.js +89 -0
  20. package/public/voice-dictation.js +45 -0
  21. package/src/bin/desktop-rehydration-smoke.mjs +111 -0
  22. package/src/bin/dextunnel.mjs +41 -0
  23. package/src/bin/doctor.mjs +48 -0
  24. package/src/bin/launch-attest.mjs +39 -0
  25. package/src/bin/launch-status.mjs +49 -0
  26. package/src/bin/mobile-link-proxy.mjs +221 -0
  27. package/src/bin/mobile-proof.mjs +164 -0
  28. package/src/bin/mobile-transport-smoke.mjs +200 -0
  29. package/src/bin/probe-codex-app-server-write.mjs +36 -0
  30. package/src/bin/probe-codex-app-server.mjs +30 -0
  31. package/src/lib/agent-room-context.mjs +54 -0
  32. package/src/lib/agent-room-runtime.mjs +355 -0
  33. package/src/lib/agent-room-service.mjs +335 -0
  34. package/src/lib/agent-room-state.mjs +406 -0
  35. package/src/lib/agent-room-store.mjs +71 -0
  36. package/src/lib/agent-room-text.mjs +48 -0
  37. package/src/lib/app-server-contract.mjs +66 -0
  38. package/src/lib/app-server-runtime.mjs +60 -0
  39. package/src/lib/attachment-service.mjs +119 -0
  40. package/src/lib/bridge-api-handler.mjs +719 -0
  41. package/src/lib/bridge-runtime-lifecycle.mjs +51 -0
  42. package/src/lib/bridge-status-builder.mjs +60 -0
  43. package/src/lib/codex-app-server-client.mjs +1511 -0
  44. package/src/lib/companion-state.mjs +453 -0
  45. package/src/lib/control-lease-service.mjs +180 -0
  46. package/src/lib/debug-harness-service.mjs +173 -0
  47. package/src/lib/desktop-integration.mjs +146 -0
  48. package/src/lib/desktop-rehydration-smoke.mjs +269 -0
  49. package/src/lib/dextunnel-cli.mjs +122 -0
  50. package/src/lib/discovery-docs.mjs +1321 -0
  51. package/src/lib/fake-codex-app-server-bridge.mjs +340 -0
  52. package/src/lib/install-preflight.mjs +373 -0
  53. package/src/lib/interaction-resolution-service.mjs +185 -0
  54. package/src/lib/interaction-state.mjs +360 -0
  55. package/src/lib/launch-release-bar.mjs +158 -0
  56. package/src/lib/live-control-state.mjs +107 -0
  57. package/src/lib/live-payload-builder.mjs +298 -0
  58. package/src/lib/live-selection-transition-state.mjs +49 -0
  59. package/src/lib/live-transcript-state.mjs +549 -0
  60. package/src/lib/mobile-network-profile.mjs +39 -0
  61. package/src/lib/mock-codex-adapter.mjs +62 -0
  62. package/src/lib/operator-diagnostics.mjs +82 -0
  63. package/src/lib/repo-changes-service.mjs +527 -0
  64. package/src/lib/runtime-config.mjs +106 -0
  65. package/src/lib/selection-state-service.mjs +214 -0
  66. package/src/lib/session-store.mjs +355 -0
  67. package/src/lib/shared-room-state.mjs +473 -0
  68. package/src/lib/shared-selection-state.mjs +40 -0
  69. package/src/lib/sse-hub.mjs +35 -0
  70. package/src/lib/static-surface-service.mjs +71 -0
  71. package/src/lib/surface-access.mjs +189 -0
  72. package/src/lib/surface-presence-service.mjs +118 -0
  73. package/src/lib/surface-request-guard.mjs +52 -0
  74. package/src/lib/thread-sync-state.mjs +536 -0
  75. package/src/lib/watcher-lifecycle.mjs +287 -0
  76. package/src/server.mjs +1446 -0
@@ -0,0 +1,287 @@
1
+ export function createWatcherLifecycleService({
2
+ appServerState,
3
+ applyWatcherNotification = () => false,
4
+ beginInteractionFlow = () => null,
5
+ broadcast = () => {},
6
+ buildLivePayload = () => ({}),
7
+ clearInteractionFlow = () => {},
8
+ codexAppServer,
9
+ invalidateRepoChangesCache = () => {},
10
+ liveState,
11
+ mapPendingInteraction = () => null,
12
+ maybeWakeCompanionForCompaction = () => {},
13
+ maybeWakeCompanionForInteractionResolution = () => {},
14
+ maybeWakeCompanionForTurnCompletion = () => {},
15
+ nowIso = () => new Date().toISOString(),
16
+ refreshSelectedThreadSnapshot = async () => {},
17
+ rememberTurnOrigin = () => {},
18
+ resetCompanionWakeups = () => {},
19
+ summarizeNotificationInteraction = () => null,
20
+ watchRefreshMethods = new Set(),
21
+ setTimeoutFn = setTimeout,
22
+ clearTimeoutFn = clearTimeout
23
+ } = {}) {
24
+ let snapshotRefreshTimer = null;
25
+ let watchReconnectTimer = null;
26
+ let watcherController = null;
27
+ let watcherToken = 0;
28
+
29
+ function scheduleSnapshotRefresh(delay = 180) {
30
+ if (snapshotRefreshTimer) {
31
+ clearTimeoutFn(snapshotRefreshTimer);
32
+ }
33
+
34
+ snapshotRefreshTimer = setTimeoutFn(() => {
35
+ snapshotRefreshTimer = null;
36
+ void refreshSelectedThreadSnapshot();
37
+ }, delay);
38
+ }
39
+
40
+ function clearWatcher() {
41
+ if (watchReconnectTimer) {
42
+ clearTimeoutFn(watchReconnectTimer);
43
+ watchReconnectTimer = null;
44
+ }
45
+
46
+ if (watcherController) {
47
+ watcherController.close();
48
+ watcherController = null;
49
+ }
50
+
51
+ liveState.watcherConnected = false;
52
+ }
53
+
54
+ function scheduleWatcherReconnect(threadId, token) {
55
+ if (watchReconnectTimer) {
56
+ clearTimeoutFn(watchReconnectTimer);
57
+ }
58
+
59
+ watchReconnectTimer = setTimeoutFn(() => {
60
+ if (token !== watcherToken || liveState.selectedThreadId !== threadId) {
61
+ return;
62
+ }
63
+
64
+ void restartWatcher();
65
+ }, 1200);
66
+ }
67
+
68
+ function handleServerRequest(request) {
69
+ liveState.pendingInteraction = mapPendingInteraction(request, beginInteractionFlow(request));
70
+ appServerState.lastInteraction = summarizeNotificationInteraction(liveState.pendingInteraction, request);
71
+ broadcast("live", buildLivePayload());
72
+ }
73
+
74
+ function handleServerRequestResolved(message, threadId) {
75
+ const resolvedRequestId = message.params?.requestId || null;
76
+ const wasPending = liveState.pendingInteraction?.requestId === resolvedRequestId;
77
+ const wasJustResponded =
78
+ appServerState.lastInteraction?.requestId === resolvedRequestId &&
79
+ appServerState.lastInteraction?.status === "responded";
80
+ const nextInteractionThreadId = message.params?.threadId || appServerState.lastInteraction?.threadId || null;
81
+
82
+ if (wasPending) {
83
+ liveState.pendingInteraction = null;
84
+ }
85
+
86
+ appServerState.lastInteraction = {
87
+ action: appServerState.lastInteraction?.action || null,
88
+ at: nowIso(),
89
+ flowContinuation: appServerState.lastInteraction?.flowContinuation || "",
90
+ flowLabel: appServerState.lastInteraction?.flowLabel || "",
91
+ flowStep: appServerState.lastInteraction?.flowStep || null,
92
+ itemId: appServerState.lastInteraction?.itemId || null,
93
+ kind: appServerState.lastInteraction?.kind || "interaction",
94
+ kindLabel: appServerState.lastInteraction?.kindLabel || null,
95
+ requestId: resolvedRequestId,
96
+ retryAttempt: appServerState.lastInteraction?.retryAttempt || 1,
97
+ summary: appServerState.lastInteraction?.summary || null,
98
+ source: "app-server",
99
+ status: wasPending || wasJustResponded ? "resolved" : "cleared",
100
+ threadId: nextInteractionThreadId,
101
+ turnId: appServerState.lastInteraction?.turnId || null
102
+ };
103
+
104
+ if (appServerState.lastInteraction.status === "resolved") {
105
+ maybeWakeCompanionForInteractionResolution({
106
+ interaction: appServerState.lastInteraction,
107
+ threadId: nextInteractionThreadId
108
+ });
109
+ }
110
+
111
+ broadcast("live", buildLivePayload());
112
+ scheduleSnapshotRefresh(60);
113
+ }
114
+
115
+ function handleTurnStarted(message, threadId, cwd) {
116
+ invalidateRepoChangesCache({ cwd });
117
+ rememberTurnOrigin(
118
+ threadId,
119
+ message.params?.turn?.id || null,
120
+ liveState.writeLock?.source || null
121
+ );
122
+ resetCompanionWakeups(message.params?.threadId || threadId, { preserveLastWake: true });
123
+ liveState.writeLock = {
124
+ at: nowIso(),
125
+ source: liveState.writeLock?.source || "external",
126
+ status: "running",
127
+ threadId
128
+ };
129
+ liveState.turnDiff = {
130
+ cwd,
131
+ diff: "",
132
+ threadId,
133
+ turnId: message.params?.turn?.id || null,
134
+ updatedAt: nowIso()
135
+ };
136
+ applyWatcherNotification(message, { threadId, cwd });
137
+ broadcast("live", buildLivePayload());
138
+ }
139
+
140
+ function handleTurnCompleted(message, threadId, cwd) {
141
+ invalidateRepoChangesCache({ cwd });
142
+ clearInteractionFlow({ threadId: message.params?.threadId || threadId });
143
+ liveState.writeLock = null;
144
+ applyWatcherNotification(message, { threadId, cwd });
145
+ maybeWakeCompanionForTurnCompletion({
146
+ threadId: message.params?.threadId || threadId,
147
+ turnId: message.params?.turn?.id || message.params?.turnId || null
148
+ });
149
+ broadcast("live", buildLivePayload());
150
+ scheduleSnapshotRefresh(80);
151
+ }
152
+
153
+ function handleTurnDiffUpdated(message, threadId, cwd) {
154
+ invalidateRepoChangesCache({ cwd });
155
+ liveState.turnDiff = {
156
+ cwd,
157
+ diff: message.params?.diff || "",
158
+ threadId: message.params?.threadId || threadId,
159
+ turnId: message.params?.turnId || liveState.turnDiff?.turnId || null,
160
+ updatedAt: nowIso()
161
+ };
162
+ broadcast("live", buildLivePayload());
163
+ }
164
+
165
+ function handleNotification(message, threadId, cwd) {
166
+ if (message.method === "serverRequest/resolved") {
167
+ handleServerRequestResolved(message, threadId);
168
+ return;
169
+ }
170
+
171
+ if (message.method === "turn/started") {
172
+ handleTurnStarted(message, threadId, cwd);
173
+ return;
174
+ }
175
+
176
+ if (message.method === "turn/completed") {
177
+ handleTurnCompleted(message, threadId, cwd);
178
+ return;
179
+ }
180
+
181
+ if (message.method === "turn/diff/updated") {
182
+ handleTurnDiffUpdated(message, threadId, cwd);
183
+ return;
184
+ }
185
+
186
+ if (applyWatcherNotification(message, { threadId, cwd })) {
187
+ if (message.method === "thread/compacted") {
188
+ maybeWakeCompanionForCompaction({
189
+ threadId: message.params?.threadId || threadId,
190
+ turnId: message.params?.turnId || null
191
+ });
192
+ }
193
+ broadcast("live", buildLivePayload());
194
+ return;
195
+ }
196
+
197
+ if (watchRefreshMethods.has(message.method)) {
198
+ scheduleSnapshotRefresh();
199
+ }
200
+ }
201
+
202
+ async function restartWatcher() {
203
+ watcherToken += 1;
204
+ const token = watcherToken;
205
+ const threadId = liveState.selectedThreadId;
206
+ const cwd = liveState.selectedProjectCwd;
207
+
208
+ clearWatcher();
209
+ clearInteractionFlow({ threadId });
210
+ liveState.pendingInteraction = null;
211
+ broadcast("live", buildLivePayload());
212
+
213
+ if (!threadId) {
214
+ return;
215
+ }
216
+
217
+ try {
218
+ watcherController = await codexAppServer.watchThread({
219
+ threadId,
220
+ cwd,
221
+ onClose() {
222
+ if (token !== watcherToken) {
223
+ return;
224
+ }
225
+
226
+ clearInteractionFlow({ threadId });
227
+ liveState.pendingInteraction = null;
228
+ liveState.watcherConnected = false;
229
+ broadcast("live", buildLivePayload());
230
+ scheduleWatcherReconnect(threadId, token);
231
+ },
232
+ onError(error) {
233
+ if (token !== watcherToken) {
234
+ return;
235
+ }
236
+
237
+ liveState.lastError = error.message;
238
+ clearInteractionFlow({ threadId });
239
+ liveState.pendingInteraction = null;
240
+ liveState.watcherConnected = false;
241
+ broadcast("live", buildLivePayload());
242
+ },
243
+ onReady() {
244
+ if (token !== watcherToken) {
245
+ return;
246
+ }
247
+
248
+ liveState.watcherConnected = true;
249
+ liveState.lastError = null;
250
+ scheduleSnapshotRefresh(0);
251
+ broadcast("live", buildLivePayload());
252
+ },
253
+ onServerRequest(request) {
254
+ if (token !== watcherToken) {
255
+ return;
256
+ }
257
+
258
+ handleServerRequest(request);
259
+ },
260
+ onNotification(message) {
261
+ if (token !== watcherToken) {
262
+ return;
263
+ }
264
+
265
+ handleNotification(message, threadId, cwd);
266
+ }
267
+ });
268
+ } catch (error) {
269
+ if (token !== watcherToken) {
270
+ return;
271
+ }
272
+
273
+ liveState.lastError = error.message;
274
+ liveState.watcherConnected = false;
275
+ broadcast("live", buildLivePayload());
276
+ scheduleWatcherReconnect(threadId, token);
277
+ }
278
+ }
279
+
280
+ return {
281
+ clearWatcher,
282
+ getWatcherController: () => watcherController,
283
+ hasWatcherController: () => Boolean(watcherController),
284
+ restartWatcher,
285
+ scheduleSnapshotRefresh
286
+ };
287
+ }