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,406 @@
1
+ import { normalizeAgentRoomReply } from "./agent-room-text.mjs";
2
+
3
+ export const AGENT_ROOM_MEMBER_IDS = ["nix", "spark", "gemini", "claude", "oracle"];
4
+ const ROOM_MESSAGE_LIMIT = 120;
5
+
6
+ function isoNow() {
7
+ return new Date().toISOString();
8
+ }
9
+
10
+ function uniqueIds(values = []) {
11
+ const seen = new Set();
12
+ const result = [];
13
+ for (const value of values) {
14
+ const id = String(value || "").trim().toLowerCase();
15
+ if (!id || seen.has(id)) {
16
+ continue;
17
+ }
18
+ seen.add(id);
19
+ result.push(id);
20
+ }
21
+ return result;
22
+ }
23
+
24
+ function normalizeMemberIds(memberIds = AGENT_ROOM_MEMBER_IDS) {
25
+ const normalized = uniqueIds(memberIds).filter((id) => AGENT_ROOM_MEMBER_IDS.includes(id));
26
+ return normalized.length ? normalized : [...AGENT_ROOM_MEMBER_IDS];
27
+ }
28
+
29
+ function normalizeRoundMemberIds(memberIds, fallback = []) {
30
+ if (!Array.isArray(memberIds)) {
31
+ return Array.isArray(fallback) ? [...fallback] : [];
32
+ }
33
+
34
+ return uniqueIds(memberIds).filter((id) => AGENT_ROOM_MEMBER_IDS.includes(id));
35
+ }
36
+
37
+ function normalizeRoundAttemptMap(rawValue, participantIds = []) {
38
+ const source = rawValue && typeof rawValue === "object" ? rawValue : {};
39
+ const result = {};
40
+ for (const participantId of participantIds) {
41
+ const value = Number(source[participantId] || 0);
42
+ result[participantId] = Number.isFinite(value) && value > 0 ? value : 0;
43
+ }
44
+ return result;
45
+ }
46
+
47
+ function normalizeRoundErrorMap(rawValue, participantIds = []) {
48
+ const source = rawValue && typeof rawValue === "object" ? rawValue : {};
49
+ const result = {};
50
+ for (const participantId of participantIds) {
51
+ const value = String(source[participantId] || "").trim();
52
+ result[participantId] = value || null;
53
+ }
54
+ return result;
55
+ }
56
+
57
+ function clampMessages(messages = []) {
58
+ if (!Array.isArray(messages) || messages.length <= ROOM_MESSAGE_LIMIT) {
59
+ return Array.isArray(messages) ? messages : [];
60
+ }
61
+
62
+ return messages.slice(-ROOM_MESSAGE_LIMIT);
63
+ }
64
+
65
+ export function defaultAgentRoomState({
66
+ enabled = false,
67
+ memberIds = AGENT_ROOM_MEMBER_IDS,
68
+ threadId = "",
69
+ timestamp = isoNow()
70
+ } = {}) {
71
+ return {
72
+ currentRound: null,
73
+ enabled: Boolean(enabled),
74
+ memberIds: normalizeMemberIds(memberIds),
75
+ messages: [],
76
+ threadId: String(threadId || "").trim() || null,
77
+ updatedAt: timestamp
78
+ };
79
+ }
80
+
81
+ export function normalizeAgentRoomState(rawState, { threadId = "", timestamp = isoNow() } = {}) {
82
+ const base = rawState && typeof rawState === "object" ? rawState : defaultAgentRoomState({ threadId, timestamp });
83
+ const nextThreadId = String(threadId || base.threadId || "").trim() || null;
84
+ const memberIds = normalizeMemberIds(base.memberIds);
85
+ const messages = clampMessages(
86
+ (Array.isArray(base.messages) ? base.messages : [])
87
+ .filter((message) => message && typeof message === "object")
88
+ .map((message) => {
89
+ const participantId =
90
+ String(message.participantId || message.lane || message.origin || "").trim().toLowerCase() || "system";
91
+ return {
92
+ id: String(message.id || "").trim() || null,
93
+ lane: String(message.lane || message.participantId || message.origin || "").trim().toLowerCase() || "system",
94
+ note: String(message.note || "").trim() || null,
95
+ origin: String(message.origin || message.participantId || message.lane || "").trim().toLowerCase() || "system",
96
+ participantId,
97
+ role: message.role === "user" ? "user" : "assistant",
98
+ roundId: String(message.roundId || "").trim() || null,
99
+ text:
100
+ message.role === "user"
101
+ ? String(message.text || "").trim()
102
+ : normalizeAgentRoomReply(participantId, message.text || ""),
103
+ timestamp: String(message.timestamp || "").trim() || timestamp
104
+ };
105
+ })
106
+ .filter((message) => message.id && message.text)
107
+ .sort((a, b) => new Date(a.timestamp || 0).getTime() - new Date(b.timestamp || 0).getTime())
108
+ );
109
+
110
+ let currentRound = null;
111
+ if (base.currentRound && typeof base.currentRound === "object") {
112
+ currentRound = {
113
+ attemptsByParticipant: {},
114
+ completedAt: String(base.currentRound.completedAt || "").trim() || null,
115
+ id: String(base.currentRound.id || "").trim() || null,
116
+ lastErrorByParticipant: {},
117
+ messageId: String(base.currentRound.messageId || "").trim() || null,
118
+ participantIds: normalizeMemberIds(base.currentRound.participantIds || memberIds),
119
+ pendingParticipantIds: normalizeRoundMemberIds(base.currentRound.pendingParticipantIds, []),
120
+ promptText: String(base.currentRound.promptText || "").trim() || null,
121
+ retryCount: Math.max(0, Number(base.currentRound.retryCount || 0) || 0),
122
+ completedParticipantIds: normalizeRoundMemberIds(base.currentRound.completedParticipantIds, []),
123
+ failedParticipantIds: normalizeRoundMemberIds(base.currentRound.failedParticipantIds, []),
124
+ startedAt: String(base.currentRound.startedAt || "").trim() || timestamp,
125
+ status: String(base.currentRound.status || "").trim() || "running"
126
+ };
127
+
128
+ currentRound.attemptsByParticipant = normalizeRoundAttemptMap(
129
+ base.currentRound.attemptsByParticipant,
130
+ currentRound.participantIds
131
+ );
132
+ currentRound.lastErrorByParticipant = normalizeRoundErrorMap(
133
+ base.currentRound.lastErrorByParticipant,
134
+ currentRound.participantIds
135
+ );
136
+
137
+ if (!currentRound.id) {
138
+ currentRound = null;
139
+ }
140
+ }
141
+
142
+ return {
143
+ currentRound,
144
+ enabled: Boolean(base.enabled),
145
+ memberIds,
146
+ messages,
147
+ threadId: nextThreadId,
148
+ updatedAt: String(base.updatedAt || "").trim() || timestamp
149
+ };
150
+ }
151
+
152
+ export function setAgentRoomEnabled(state, enabled, { memberIds = null, timestamp = isoNow() } = {}) {
153
+ const current = normalizeAgentRoomState(state, { timestamp });
154
+ return {
155
+ ...current,
156
+ enabled: Boolean(enabled),
157
+ memberIds: memberIds ? normalizeMemberIds(memberIds) : current.memberIds,
158
+ updatedAt: timestamp
159
+ };
160
+ }
161
+
162
+ export function createAgentRoomMessage({
163
+ id,
164
+ participantId,
165
+ role = "assistant",
166
+ text,
167
+ timestamp = isoNow(),
168
+ roundId = null,
169
+ note = null
170
+ } = {}) {
171
+ const nextParticipantId = String(participantId || "").trim().toLowerCase() || "system";
172
+ return {
173
+ id: String(id || "").trim() || null,
174
+ lane: nextParticipantId,
175
+ note: String(note || "").trim() || null,
176
+ origin: nextParticipantId,
177
+ participantId: nextParticipantId,
178
+ role: role === "user" ? "user" : "assistant",
179
+ roundId: String(roundId || "").trim() || null,
180
+ text: String(text || "").trim(),
181
+ timestamp
182
+ };
183
+ }
184
+
185
+ export function appendAgentRoomMessages(state, messages = [], { timestamp = isoNow() } = {}) {
186
+ const current = normalizeAgentRoomState(state, { timestamp });
187
+ const nextMessages = clampMessages([
188
+ ...current.messages,
189
+ ...messages
190
+ .map((message) => normalizeAgentRoomState({
191
+ enabled: current.enabled,
192
+ memberIds: current.memberIds,
193
+ messages: [message],
194
+ threadId: current.threadId
195
+ }, { timestamp }).messages[0])
196
+ .filter(Boolean)
197
+ ]);
198
+
199
+ return {
200
+ ...current,
201
+ messages: nextMessages,
202
+ updatedAt: timestamp
203
+ };
204
+ }
205
+
206
+ export function startAgentRoomRound(
207
+ state,
208
+ {
209
+ messageId,
210
+ note = null,
211
+ participantIds = null,
212
+ promptText = null,
213
+ roundId,
214
+ retryCount = 0,
215
+ text,
216
+ timestamp = isoNow(),
217
+ userParticipantId = "remote"
218
+ } = {}
219
+ ) {
220
+ const current = normalizeAgentRoomState(state, { timestamp });
221
+ if (current.currentRound?.status === "running") {
222
+ throw new Error("The council room is already discussing something.");
223
+ }
224
+
225
+ const nextParticipantIds = normalizeMemberIds(participantIds || current.memberIds);
226
+ const nextRoundId = String(roundId || "").trim();
227
+ const nextMessageId = String(messageId || "").trim();
228
+ const userMessage = createAgentRoomMessage({
229
+ id: nextMessageId,
230
+ note: note || `council room / ${nextParticipantIds.length} participants`,
231
+ participantId: userParticipantId,
232
+ role: "user",
233
+ roundId: nextRoundId,
234
+ text,
235
+ timestamp
236
+ });
237
+
238
+ return {
239
+ ...appendAgentRoomMessages(current, [userMessage], { timestamp }),
240
+ currentRound: {
241
+ attemptsByParticipant: normalizeRoundAttemptMap(null, nextParticipantIds),
242
+ completedAt: null,
243
+ completedParticipantIds: [],
244
+ failedParticipantIds: [],
245
+ id: nextRoundId,
246
+ lastErrorByParticipant: normalizeRoundErrorMap(null, nextParticipantIds),
247
+ messageId: nextMessageId,
248
+ participantIds: nextParticipantIds,
249
+ pendingParticipantIds: [...nextParticipantIds],
250
+ promptText: String(promptText || text || "").trim() || null,
251
+ retryCount: Math.max(0, Number(retryCount || 0) || 0),
252
+ startedAt: timestamp,
253
+ status: "running"
254
+ },
255
+ enabled: true,
256
+ memberIds: current.memberIds,
257
+ updatedAt: timestamp
258
+ };
259
+ }
260
+
261
+ export function settleAgentRoomParticipant(
262
+ state,
263
+ {
264
+ error = null,
265
+ messageId,
266
+ participantId,
267
+ roundId,
268
+ text,
269
+ timestamp = isoNow()
270
+ } = {}
271
+ ) {
272
+ const current = normalizeAgentRoomState(state, { timestamp });
273
+ if (!current.currentRound || current.currentRound.id !== String(roundId || "").trim()) {
274
+ return current;
275
+ }
276
+
277
+ const nextParticipantId = String(participantId || "").trim().toLowerCase();
278
+ if (!nextParticipantId) {
279
+ return current;
280
+ }
281
+
282
+ const pendingParticipantIds = current.currentRound.pendingParticipantIds.filter((id) => id !== nextParticipantId);
283
+ const attemptsByParticipant = {
284
+ ...current.currentRound.attemptsByParticipant,
285
+ [nextParticipantId]: Number(current.currentRound.attemptsByParticipant?.[nextParticipantId] || 0) + 1
286
+ };
287
+ const lastErrorByParticipant = {
288
+ ...current.currentRound.lastErrorByParticipant,
289
+ [nextParticipantId]: error ? String(error || "").trim() || "Unknown error." : null
290
+ };
291
+ const failedParticipantIds = error
292
+ ? uniqueIds([...current.currentRound.failedParticipantIds, nextParticipantId])
293
+ : current.currentRound.failedParticipantIds.filter((id) => id !== nextParticipantId);
294
+ const completedParticipantIds = error
295
+ ? current.currentRound.completedParticipantIds.filter((id) => id !== nextParticipantId)
296
+ : uniqueIds([...current.currentRound.completedParticipantIds, nextParticipantId]);
297
+ const done = pendingParticipantIds.length === 0;
298
+ const status = done ? (failedParticipantIds.length ? "partial" : "complete") : "running";
299
+ const failureNote = /timeout/i.test(String(error || ""))
300
+ ? "lane timeout"
301
+ : /malformed/i.test(String(error || ""))
302
+ ? "malformed reply"
303
+ : "lane failure";
304
+
305
+ const replyMessage = createAgentRoomMessage({
306
+ id: String(messageId || "").trim() || `${nextParticipantId}:${roundId}:${timestamp}`,
307
+ note: error ? failureNote : "council reply",
308
+ participantId: nextParticipantId,
309
+ role: "assistant",
310
+ roundId,
311
+ text: error ? `${nextParticipantId} failed: ${String(error || "Unknown error.").trim()}` : text,
312
+ timestamp
313
+ });
314
+
315
+ const nextState = appendAgentRoomMessages(current, [replyMessage], { timestamp });
316
+ return {
317
+ ...nextState,
318
+ currentRound: done
319
+ ? {
320
+ ...current.currentRound,
321
+ attemptsByParticipant,
322
+ completedAt: timestamp,
323
+ completedParticipantIds,
324
+ failedParticipantIds,
325
+ lastErrorByParticipant,
326
+ pendingParticipantIds,
327
+ status
328
+ }
329
+ : {
330
+ ...current.currentRound,
331
+ attemptsByParticipant,
332
+ completedAt: null,
333
+ completedParticipantIds,
334
+ failedParticipantIds,
335
+ lastErrorByParticipant,
336
+ pendingParticipantIds,
337
+ status
338
+ },
339
+ updatedAt: timestamp
340
+ };
341
+ }
342
+
343
+ export function finalizeAgentRoomRound(state, { roundId, timestamp = isoNow() } = {}) {
344
+ const current = normalizeAgentRoomState(state, { timestamp });
345
+ if (!current.currentRound || current.currentRound.id !== String(roundId || "").trim()) {
346
+ return current;
347
+ }
348
+
349
+ return {
350
+ ...current,
351
+ currentRound: {
352
+ ...current.currentRound,
353
+ status: current.currentRound.failedParticipantIds.length ? "partial" : "complete"
354
+ },
355
+ updatedAt: timestamp
356
+ };
357
+ }
358
+
359
+ export function interruptAgentRoomRound(
360
+ state,
361
+ { note = "Council round interrupted.", timestamp = isoNow() } = {}
362
+ ) {
363
+ const current = normalizeAgentRoomState(state, { timestamp });
364
+ if (!current.currentRound) {
365
+ return current;
366
+ }
367
+
368
+ const interrupted = appendAgentRoomMessages(current, [
369
+ createAgentRoomMessage({
370
+ id: `system:${current.currentRound.id}:interrupted`,
371
+ note: "council room",
372
+ participantId: "system",
373
+ role: "assistant",
374
+ roundId: current.currentRound.id,
375
+ text: note,
376
+ timestamp
377
+ })
378
+ ], { timestamp });
379
+
380
+ return {
381
+ ...interrupted,
382
+ currentRound: null,
383
+ updatedAt: timestamp
384
+ };
385
+ }
386
+
387
+ export function getAgentRoomRetryRound(state) {
388
+ const current = normalizeAgentRoomState(state);
389
+ const round = current.currentRound;
390
+ if (!round || round.status === "running") {
391
+ return null;
392
+ }
393
+
394
+ const participantIds = round.failedParticipantIds || [];
395
+ if (!participantIds.length || !round.promptText) {
396
+ return null;
397
+ }
398
+
399
+ return {
400
+ note: `council retry / ${participantIds.length} participants`,
401
+ participantIds: [...participantIds],
402
+ promptText: round.promptText,
403
+ retryCount: Number(round.retryCount || 0) + 1,
404
+ roundId: round.id
405
+ };
406
+ }
@@ -0,0 +1,71 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ defaultAgentRoomState,
6
+ interruptAgentRoomRound,
7
+ normalizeAgentRoomState
8
+ } from "./agent-room-state.mjs";
9
+
10
+ function sanitizeThreadId(threadId = "") {
11
+ const normalized = String(threadId || "").trim().replace(/[^a-zA-Z0-9._-]+/g, "-");
12
+ return normalized || "thread";
13
+ }
14
+
15
+ export function createAgentRoomStore({
16
+ baseDir,
17
+ now = () => new Date().toISOString()
18
+ } = {}) {
19
+ if (!baseDir) {
20
+ throw new Error("createAgentRoomStore requires a baseDir.");
21
+ }
22
+
23
+ function filePathForThread(threadId) {
24
+ return path.join(baseDir, `${sanitizeThreadId(threadId)}.json`);
25
+ }
26
+
27
+ async function load(threadId) {
28
+ const id = String(threadId || "").trim();
29
+ if (!id) {
30
+ return defaultAgentRoomState();
31
+ }
32
+
33
+ try {
34
+ const raw = JSON.parse(await readFile(filePathForThread(id), "utf8"));
35
+ const normalized = normalizeAgentRoomState(raw, { threadId: id, timestamp: now() });
36
+ if (normalized.currentRound?.status === "running") {
37
+ return interruptAgentRoomRound(normalized, {
38
+ note: "Council round stopped when Dextunnel restarted.",
39
+ timestamp: now()
40
+ });
41
+ }
42
+ return normalized;
43
+ } catch {
44
+ return defaultAgentRoomState({
45
+ enabled: false,
46
+ threadId: id,
47
+ timestamp: now()
48
+ });
49
+ }
50
+ }
51
+
52
+ async function save(threadId, state) {
53
+ const id = String(threadId || "").trim();
54
+ if (!id) {
55
+ return;
56
+ }
57
+
58
+ await mkdir(baseDir, { recursive: true });
59
+ await writeFile(
60
+ filePathForThread(id),
61
+ JSON.stringify(normalizeAgentRoomState(state, { threadId: id, timestamp: now() }), null, 2),
62
+ "utf8"
63
+ );
64
+ }
65
+
66
+ return {
67
+ filePathForThread,
68
+ load,
69
+ save
70
+ };
71
+ }
@@ -0,0 +1,48 @@
1
+ function stripTrailingTelemetryLines(text = "") {
2
+ const lines = String(text || "").split("\n");
3
+ while (lines.length > 0) {
4
+ const lastLine = String(lines.at(-1) || "").trim();
5
+ if (!lastLine) {
6
+ lines.pop();
7
+ continue;
8
+ }
9
+ if (/^files=\d+\s*$/i.test(lastLine)) {
10
+ lines.pop();
11
+ continue;
12
+ }
13
+ if (/^\d+[mh]\d*[sm]?(?:\s+\d+[sm])?\s+·\s+/i.test(lastLine) || /^\d+(?:\.\d+)?s\s+·\s+/i.test(lastLine)) {
14
+ lines.pop();
15
+ continue;
16
+ }
17
+ break;
18
+ }
19
+ return lines.join("\n").trim();
20
+ }
21
+
22
+ export function normalizeAgentRoomReply(participantId, rawText = "") {
23
+ let text = String(rawText || "").replace(/\r\n/g, "\n").trim();
24
+ if (!text) {
25
+ return "";
26
+ }
27
+
28
+ const answerMatch = /(?:^|\n)Answer:\s*/g;
29
+ let lastAnswerIndex = -1;
30
+ for (const match of text.matchAll(answerMatch)) {
31
+ lastAnswerIndex = match.index + match[0].length;
32
+ }
33
+ if (lastAnswerIndex >= 0) {
34
+ text = text.slice(lastAnswerIndex).trim();
35
+ }
36
+
37
+ text = stripTrailingTelemetryLines(text);
38
+
39
+ if (participantId === "oracle" && lastAnswerIndex < 0) {
40
+ text = text
41
+ .replace(/^.*?Launching .*?\n/si, "")
42
+ .replace(/^[^\n]*oracle[^\n]*\n/i, "")
43
+ .trim();
44
+ text = stripTrailingTelemetryLines(text);
45
+ }
46
+
47
+ return text;
48
+ }
@@ -0,0 +1,66 @@
1
+ export const APP_SERVER_RPC_METHODS = [
2
+ "thread/list",
3
+ "thread/read",
4
+ "thread/resume",
5
+ "thread/start",
6
+ "turn/start",
7
+ "turn/steer",
8
+ "turn/interrupt"
9
+ ];
10
+
11
+ export const APP_SERVER_NOTIFICATION_METHODS = [
12
+ "thread/status/changed",
13
+ "thread/name/updated",
14
+ "thread/tokenUsage/updated",
15
+ "turn/started",
16
+ "turn/completed",
17
+ "item/completed",
18
+ "item/commandExecution/requestApproval",
19
+ "item/fileChange/requestApproval",
20
+ "item/permissions/requestApproval",
21
+ "item/tool/requestUserInput",
22
+ "item/tool/call",
23
+ "mcpServer/elicitation/request"
24
+ ];
25
+
26
+ export const APP_SERVER_LIVE_PATCH_NOTIFICATION_METHODS = [
27
+ "thread/status/changed",
28
+ "thread/name/updated",
29
+ "thread/tokenUsage/updated",
30
+ "turn/started",
31
+ "turn/completed",
32
+ "turn/plan/updated",
33
+ "turn/diff/updated",
34
+ "item/started",
35
+ "item/completed",
36
+ "item/agentMessage/delta",
37
+ "item/plan/delta",
38
+ "item/reasoning/summaryTextDelta",
39
+ "item/reasoning/textDelta",
40
+ "item/fileChange/outputDelta",
41
+ "thread/compacted"
42
+ ];
43
+
44
+ export const APP_SERVER_SERVER_REQUEST_METHODS = [
45
+ "account/chatgptAuthTokens/refresh",
46
+ "item/commandExecution/requestApproval",
47
+ "item/fileChange/requestApproval",
48
+ "item/permissions/requestApproval",
49
+ "item/tool/requestUserInput",
50
+ "mcpServer/elicitation/request",
51
+ "item/tool/call"
52
+ ];
53
+
54
+ export const APP_SERVER_ITEM_TYPES = [
55
+ "userMessage",
56
+ "agentMessage",
57
+ "commandExecution",
58
+ "reasoning",
59
+ "contextCompaction",
60
+ "mcpToolCall",
61
+ "dynamicToolCall",
62
+ "collabToolCall",
63
+ "fileChange"
64
+ ];
65
+
66
+ export const APP_SERVER_DRIFT_RUNBOOK_PATH = ".maintainer/ops/app-server-drift-runbook.md";
@@ -0,0 +1,60 @@
1
+ import { createCodexAppServerBridge } from "./codex-app-server-client.mjs";
2
+ import { createFakeCodexAppServerBridge } from "./fake-codex-app-server-bridge.mjs";
3
+
4
+ export function createAppServerState() {
5
+ return {
6
+ lastControlEvent: null,
7
+ lastInteraction: null,
8
+ lastSelectionEvent: null,
9
+ lastSurfaceEvent: null,
10
+ lastWrite: null
11
+ };
12
+ }
13
+
14
+ export function createLiveState({ cwd = process.cwd() } = {}) {
15
+ return {
16
+ agentRoomByThreadId: {},
17
+ companionByThreadId: {},
18
+ controlLease: null,
19
+ interactionFlow: null,
20
+ lastError: null,
21
+ lastSyncAt: null,
22
+ pendingInteraction: null,
23
+ selectedProjectCwd: cwd,
24
+ selectionSource: "remote",
25
+ surfacePresenceByClientId: {},
26
+ selectedThreadId: null,
27
+ selectedThreadSnapshot: null,
28
+ threads: [],
29
+ turnDiff: null,
30
+ turnOriginsByThreadId: {},
31
+ watcherConnected: false,
32
+ writeLock: null
33
+ };
34
+ }
35
+
36
+ export function createCodexRuntime({
37
+ binaryPath,
38
+ cwd = process.cwd(),
39
+ listenUrl,
40
+ useFakeAppServer = false,
41
+ fakeSendDelayMs = 0
42
+ } = {}) {
43
+ const codexAppServer = useFakeAppServer
44
+ ? createFakeCodexAppServerBridge({
45
+ binaryPath,
46
+ cwd,
47
+ listenUrl,
48
+ sendDelayMs: fakeSendDelayMs
49
+ })
50
+ : createCodexAppServerBridge({
51
+ binaryPath,
52
+ listenUrl
53
+ });
54
+
55
+ return {
56
+ appServerState: createAppServerState(),
57
+ codexAppServer,
58
+ liveState: createLiveState({ cwd })
59
+ };
60
+ }