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.
- package/LICENSE +211 -0
- package/README.md +112 -0
- package/SECURITY.md +27 -0
- package/SUPPORT.md +43 -0
- package/package.json +44 -0
- package/public/client-shared.js +1831 -0
- package/public/favicon.svg +11 -0
- package/public/host.html +29 -0
- package/public/host.js +2079 -0
- package/public/index.html +28 -0
- package/public/index.js +98 -0
- package/public/live-bridge-lifecycle.js +258 -0
- package/public/live-bridge-retry-state.js +61 -0
- package/public/live-selection-intent.js +79 -0
- package/public/remote-operator-state.js +316 -0
- package/public/remote.html +167 -0
- package/public/remote.js +3967 -0
- package/public/styles.css +2793 -0
- package/public/surface-view-state.js +89 -0
- package/public/voice-dictation.js +45 -0
- package/src/bin/desktop-rehydration-smoke.mjs +111 -0
- package/src/bin/dextunnel.mjs +41 -0
- package/src/bin/doctor.mjs +48 -0
- package/src/bin/launch-attest.mjs +39 -0
- package/src/bin/launch-status.mjs +49 -0
- package/src/bin/mobile-link-proxy.mjs +221 -0
- package/src/bin/mobile-proof.mjs +164 -0
- package/src/bin/mobile-transport-smoke.mjs +200 -0
- package/src/bin/probe-codex-app-server-write.mjs +36 -0
- package/src/bin/probe-codex-app-server.mjs +30 -0
- package/src/lib/agent-room-context.mjs +54 -0
- package/src/lib/agent-room-runtime.mjs +355 -0
- package/src/lib/agent-room-service.mjs +335 -0
- package/src/lib/agent-room-state.mjs +406 -0
- package/src/lib/agent-room-store.mjs +71 -0
- package/src/lib/agent-room-text.mjs +48 -0
- package/src/lib/app-server-contract.mjs +66 -0
- package/src/lib/app-server-runtime.mjs +60 -0
- package/src/lib/attachment-service.mjs +119 -0
- package/src/lib/bridge-api-handler.mjs +719 -0
- package/src/lib/bridge-runtime-lifecycle.mjs +51 -0
- package/src/lib/bridge-status-builder.mjs +60 -0
- package/src/lib/codex-app-server-client.mjs +1511 -0
- package/src/lib/companion-state.mjs +453 -0
- package/src/lib/control-lease-service.mjs +180 -0
- package/src/lib/debug-harness-service.mjs +173 -0
- package/src/lib/desktop-integration.mjs +146 -0
- package/src/lib/desktop-rehydration-smoke.mjs +269 -0
- package/src/lib/dextunnel-cli.mjs +122 -0
- package/src/lib/discovery-docs.mjs +1321 -0
- package/src/lib/fake-codex-app-server-bridge.mjs +340 -0
- package/src/lib/install-preflight.mjs +373 -0
- package/src/lib/interaction-resolution-service.mjs +185 -0
- package/src/lib/interaction-state.mjs +360 -0
- package/src/lib/launch-release-bar.mjs +158 -0
- package/src/lib/live-control-state.mjs +107 -0
- package/src/lib/live-payload-builder.mjs +298 -0
- package/src/lib/live-selection-transition-state.mjs +49 -0
- package/src/lib/live-transcript-state.mjs +549 -0
- package/src/lib/mobile-network-profile.mjs +39 -0
- package/src/lib/mock-codex-adapter.mjs +62 -0
- package/src/lib/operator-diagnostics.mjs +82 -0
- package/src/lib/repo-changes-service.mjs +527 -0
- package/src/lib/runtime-config.mjs +106 -0
- package/src/lib/selection-state-service.mjs +214 -0
- package/src/lib/session-store.mjs +355 -0
- package/src/lib/shared-room-state.mjs +473 -0
- package/src/lib/shared-selection-state.mjs +40 -0
- package/src/lib/sse-hub.mjs +35 -0
- package/src/lib/static-surface-service.mjs +71 -0
- package/src/lib/surface-access.mjs +189 -0
- package/src/lib/surface-presence-service.mjs +118 -0
- package/src/lib/surface-request-guard.mjs +52 -0
- package/src/lib/thread-sync-state.mjs +536 -0
- package/src/lib/watcher-lifecycle.mjs +287 -0
- package/src/server.mjs +1446 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
export function cloneReplyAttachments(attachments = []) {
|
|
2
|
+
return attachments.map((attachment) => ({
|
|
3
|
+
dataUrl: attachment.dataUrl,
|
|
4
|
+
id: attachment.id,
|
|
5
|
+
name: attachment.name,
|
|
6
|
+
size: attachment.size,
|
|
7
|
+
type: attachment.type
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function scopedThreadStorageKey({
|
|
12
|
+
prefix = "",
|
|
13
|
+
scopeId = "",
|
|
14
|
+
threadId = ""
|
|
15
|
+
} = {}) {
|
|
16
|
+
const normalizedPrefix = String(prefix || "");
|
|
17
|
+
const normalizedScopeId = String(scopeId || "").trim() || "default";
|
|
18
|
+
const normalizedThreadId = String(threadId || "").trim() || "none";
|
|
19
|
+
return `${normalizedPrefix}${normalizedScopeId}:${normalizedThreadId}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createQueuedReply({
|
|
23
|
+
attachments = [],
|
|
24
|
+
queuedAt = new Date().toISOString(),
|
|
25
|
+
rawText = "",
|
|
26
|
+
sequence = 1,
|
|
27
|
+
threadId = ""
|
|
28
|
+
} = {}) {
|
|
29
|
+
const nextThreadId = String(threadId || "").trim();
|
|
30
|
+
if (!nextThreadId) {
|
|
31
|
+
throw new Error("No live session selected.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
attachments: cloneReplyAttachments(attachments),
|
|
36
|
+
id: `queued-reply-${Date.now()}-${sequence}`,
|
|
37
|
+
queuedAt,
|
|
38
|
+
text: String(rawText || "").trim(),
|
|
39
|
+
threadId: nextThreadId
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function queueSummary(count = 0) {
|
|
44
|
+
const nextCount = Number(count || 0);
|
|
45
|
+
if (nextCount <= 0) {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return nextCount === 1 ? "1 queued" : `${nextCount} queued`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function sessionBlockedReason({
|
|
53
|
+
hasLiveThread = false,
|
|
54
|
+
watcherConnected = true
|
|
55
|
+
} = {}) {
|
|
56
|
+
if (!hasLiveThread) {
|
|
57
|
+
return "No live session selected.";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!watcherConnected) {
|
|
61
|
+
return "Live watcher offline.";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function composeBlockedReason({
|
|
68
|
+
pendingInteraction = false,
|
|
69
|
+
sessionReason = ""
|
|
70
|
+
} = {}) {
|
|
71
|
+
if (sessionReason) {
|
|
72
|
+
return sessionReason;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (pendingInteraction) {
|
|
76
|
+
return "Resolve the pending action first.";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function controlBlockedReason({
|
|
83
|
+
hasAnyRemoteControl = false,
|
|
84
|
+
hasRemoteControl = false,
|
|
85
|
+
ownerLabel = "",
|
|
86
|
+
threadId = ""
|
|
87
|
+
} = {}) {
|
|
88
|
+
if (!threadId) {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hasAnyRemoteControl && !hasRemoteControl) {
|
|
93
|
+
return `${ownerLabel || "Another remote surface"} currently has control.`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return hasRemoteControl ? "" : "Take control to send from remote.";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function sendBlockedReason({
|
|
100
|
+
hasAnyRemoteControl = false,
|
|
101
|
+
hasRemoteControl = false,
|
|
102
|
+
ownerLabel = "",
|
|
103
|
+
pendingInteraction = false,
|
|
104
|
+
sessionReason = "",
|
|
105
|
+
threadId = ""
|
|
106
|
+
} = {}) {
|
|
107
|
+
return (
|
|
108
|
+
composeBlockedReason({
|
|
109
|
+
pendingInteraction,
|
|
110
|
+
sessionReason
|
|
111
|
+
}) ||
|
|
112
|
+
controlBlockedReason({
|
|
113
|
+
hasAnyRemoteControl,
|
|
114
|
+
hasRemoteControl,
|
|
115
|
+
ownerLabel,
|
|
116
|
+
threadId
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function controlClaimRequired(blockedReason = "") {
|
|
122
|
+
return String(blockedReason || "").trim() === "Take control to send from remote.";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function threadBusy({
|
|
126
|
+
activeTurnId = "",
|
|
127
|
+
isSendingReply = false,
|
|
128
|
+
threadStatus = "",
|
|
129
|
+
writeLockStatus = ""
|
|
130
|
+
} = {}) {
|
|
131
|
+
const normalizedStatus =
|
|
132
|
+
typeof threadStatus === "string"
|
|
133
|
+
? threadStatus.trim().toLowerCase()
|
|
134
|
+
: (
|
|
135
|
+
threadStatus &&
|
|
136
|
+
typeof threadStatus === "object" &&
|
|
137
|
+
String(threadStatus.type || threadStatus.status || "").trim().toLowerCase()
|
|
138
|
+
) || "";
|
|
139
|
+
const statusBusy = ["inprogress", "running"].includes(normalizedStatus);
|
|
140
|
+
return Boolean(isSendingReply || writeLockStatus || activeTurnId || statusBusy);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function canQueueReply({
|
|
144
|
+
controlActive = false,
|
|
145
|
+
hasDraftPayload = false,
|
|
146
|
+
isControlling = false,
|
|
147
|
+
isSelecting = false,
|
|
148
|
+
isSendingReply = false,
|
|
149
|
+
pendingInteraction = false,
|
|
150
|
+
queuedCount = 0,
|
|
151
|
+
sessionBlocked = false,
|
|
152
|
+
threadBusy: busy = false,
|
|
153
|
+
threadId = ""
|
|
154
|
+
} = {}) {
|
|
155
|
+
return Boolean(
|
|
156
|
+
threadId &&
|
|
157
|
+
!sessionBlocked &&
|
|
158
|
+
!pendingInteraction &&
|
|
159
|
+
!isSendingReply &&
|
|
160
|
+
!isSelecting &&
|
|
161
|
+
!isControlling &&
|
|
162
|
+
hasDraftPayload
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function canSteerReply({
|
|
167
|
+
blockedReason = "",
|
|
168
|
+
hasDraftPayload = false,
|
|
169
|
+
isControlling = false,
|
|
170
|
+
isDictating = false,
|
|
171
|
+
isSelecting = false,
|
|
172
|
+
isSendingReply = false,
|
|
173
|
+
threadBusy: busy = false,
|
|
174
|
+
threadId = ""
|
|
175
|
+
} = {}) {
|
|
176
|
+
return Boolean(
|
|
177
|
+
threadId &&
|
|
178
|
+
hasDraftPayload &&
|
|
179
|
+
!isSendingReply &&
|
|
180
|
+
!isSelecting &&
|
|
181
|
+
!isControlling &&
|
|
182
|
+
!isDictating &&
|
|
183
|
+
!busy &&
|
|
184
|
+
(!blockedReason || controlClaimRequired(blockedReason))
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function defaultComposerStatus({
|
|
189
|
+
blockedReason = "",
|
|
190
|
+
composerStatus = "Ready",
|
|
191
|
+
composerStatusTone = "neutral",
|
|
192
|
+
controlActive = false,
|
|
193
|
+
hasDraftPayload = false,
|
|
194
|
+
isSendingReply = false,
|
|
195
|
+
queuedCount = 0,
|
|
196
|
+
threadBusy: busy = false
|
|
197
|
+
} = {}) {
|
|
198
|
+
if (!isSendingReply && blockedReason && composerStatusTone === "neutral") {
|
|
199
|
+
return blockedReason;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const queued = queueSummary(queuedCount);
|
|
203
|
+
if (!isSendingReply && queued && composerStatusTone === "neutral") {
|
|
204
|
+
return busy ? `${queued}. Waiting for idle.` : `${queued}. Sending soon.`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!isSendingReply && !controlActive && composerStatusTone === "neutral" && hasDraftPayload) {
|
|
208
|
+
return "Steer now takes control. Queue stays local.";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!isSendingReply && busy && composerStatusTone === "neutral") {
|
|
212
|
+
return controlActive ? "Codex is busy. Queue your next steer." : "Codex is busy. Queue now; it will send when idle.";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return composerStatus;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function controlReleaseStatus({ hasDraft = false } = {}) {
|
|
219
|
+
return hasDraft ? "Remote control released. Draft kept." : "Remote control released.";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function controlEventStatus({
|
|
223
|
+
event = null,
|
|
224
|
+
hasDraft = false,
|
|
225
|
+
isLocalActor = false,
|
|
226
|
+
queuedCount = 0
|
|
227
|
+
} = {}) {
|
|
228
|
+
if (!event?.action) {
|
|
229
|
+
return "";
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const queued = queueSummary(queuedCount);
|
|
233
|
+
|
|
234
|
+
if (event.action === "claim") {
|
|
235
|
+
if (!isLocalActor) {
|
|
236
|
+
return "";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (queued) {
|
|
240
|
+
return `Remote control reclaimed. ${queued}. Sending soon.`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (hasDraft) {
|
|
244
|
+
return "Remote control active. Draft ready.";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return "Remote control active.";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (event.action !== "release") {
|
|
251
|
+
return "";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (event.cause === "expired") {
|
|
255
|
+
if (queued) {
|
|
256
|
+
return `Remote control expired. ${queued}. Waiting for control.`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return hasDraft ? "Remote control expired. Draft kept." : "Remote control expired.";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (event.actor === "host") {
|
|
263
|
+
if (queued) {
|
|
264
|
+
return `Remote control released by host. ${queued}. Waiting for control.`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return hasDraft ? "Remote control released by host. Draft kept." : "Remote control released by host.";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (queued) {
|
|
271
|
+
return `Remote control released. ${queued}. Waiting for control.`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return controlReleaseStatus({ hasDraft });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function controlReleaseFeedback({
|
|
278
|
+
eventAction = "",
|
|
279
|
+
hasDraft = false,
|
|
280
|
+
isControlling = false,
|
|
281
|
+
isSendingReply = false,
|
|
282
|
+
previousHadRemoteControl = false,
|
|
283
|
+
nextHasRemoteControl = false
|
|
284
|
+
} = {}) {
|
|
285
|
+
if (isControlling || isSendingReply) {
|
|
286
|
+
return "";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (eventAction === "release") {
|
|
290
|
+
return controlReleaseStatus({ hasDraft });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (previousHadRemoteControl && !nextHasRemoteControl) {
|
|
294
|
+
return controlReleaseStatus({ hasDraft });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function shouldFlushQueuedReplies({
|
|
301
|
+
blockedReason = "",
|
|
302
|
+
hasInFlight = false,
|
|
303
|
+
isSendingReply = false,
|
|
304
|
+
queuedCount = 0,
|
|
305
|
+
threadBusy: busy = false,
|
|
306
|
+
threadId = ""
|
|
307
|
+
} = {}) {
|
|
308
|
+
return Boolean(
|
|
309
|
+
threadId &&
|
|
310
|
+
queuedCount > 0 &&
|
|
311
|
+
(!blockedReason || controlClaimRequired(blockedReason)) &&
|
|
312
|
+
!busy &&
|
|
313
|
+
!isSendingReply &&
|
|
314
|
+
!hasInFlight
|
|
315
|
+
);
|
|
316
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta
|
|
6
|
+
name="viewport"
|
|
7
|
+
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
|
8
|
+
>
|
|
9
|
+
<title>Dextunnel Remote</title>
|
|
10
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
11
|
+
<link rel="stylesheet" href="/styles.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body class="app-shell remote-shell">
|
|
14
|
+
<main class="terminal-shell remote-shell-main">
|
|
15
|
+
<section class="terminal-window remote-window" id="remote-window">
|
|
16
|
+
<aside id="remote-sidebar" class="remote-sidebar" aria-label="Projects and threads">
|
|
17
|
+
<div class="remote-sidebar-head">
|
|
18
|
+
<p class="remote-sidebar-kicker">Projects</p>
|
|
19
|
+
<p class="remote-sidebar-note">Threads by project</p>
|
|
20
|
+
</div>
|
|
21
|
+
<div id="remote-sidebar-groups" class="remote-sidebar-groups"></div>
|
|
22
|
+
</aside>
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
id="remote-sidebar-overlay"
|
|
26
|
+
class="remote-sidebar-overlay panel-hidden"
|
|
27
|
+
hidden
|
|
28
|
+
aria-label="Close thread menu"
|
|
29
|
+
></button>
|
|
30
|
+
|
|
31
|
+
<section class="remote-main">
|
|
32
|
+
<section class="terminal-hero remote-hero">
|
|
33
|
+
<div class="remote-hero-row">
|
|
34
|
+
<div class="remote-hero-primary">
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
id="sidebar-toggle-button"
|
|
38
|
+
class="command-button command-button-compact command-button-subtle sidebar-toggle-button"
|
|
39
|
+
aria-expanded="true"
|
|
40
|
+
aria-label="Collapse thread menu"
|
|
41
|
+
>
|
|
42
|
+
<span class="hamburger-icon" aria-hidden="true">
|
|
43
|
+
<span></span>
|
|
44
|
+
<span></span>
|
|
45
|
+
<span></span>
|
|
46
|
+
</span>
|
|
47
|
+
</button>
|
|
48
|
+
<div class="remote-hero-copy">
|
|
49
|
+
<div class="terminal-statusline">
|
|
50
|
+
<span id="remote-marquee" class="terminal-marquee">Initializing session bridge...</span>
|
|
51
|
+
</div>
|
|
52
|
+
<h1 id="remote-title">Connecting...</h1>
|
|
53
|
+
<p id="remote-target" class="terminal-subline">Waiting for a live Codex session.</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="button-row toolbar-strip remote-top-actions" aria-label="Remote controls">
|
|
57
|
+
<button type="button" id="reply-toggle-button" class="command-button button-primary">Reply</button>
|
|
58
|
+
<button type="button" id="control-toggle-button" class="command-button panel-hidden" hidden>Take Control</button>
|
|
59
|
+
<button type="button" id="refresh-button" class="command-button command-button-compact">Refresh</button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<p id="remote-scope-note" class="sync-note panel-hidden" hidden></p>
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<section class="terminal-panel remote-status-panel panel-hidden" id="remote-status-panel" hidden>
|
|
66
|
+
<div id="remote-operator-diagnostics" class="operator-diagnostics panel-hidden" hidden aria-live="polite"></div>
|
|
67
|
+
<p id="remote-ui-status" class="ui-status panel-hidden" aria-live="polite" hidden></p>
|
|
68
|
+
</section>
|
|
69
|
+
|
|
70
|
+
<section class="terminal-panel panel-emphasis panel-hidden" id="remote-action-panel" hidden>
|
|
71
|
+
<div class="section-head">
|
|
72
|
+
<div>
|
|
73
|
+
<p class="section-kicker">Pending</p>
|
|
74
|
+
<h2 id="remote-action-title">Action required</h2>
|
|
75
|
+
</div>
|
|
76
|
+
<span id="remote-action-kind" class="inline-status">Approval</span>
|
|
77
|
+
</div>
|
|
78
|
+
<div id="remote-action-card" class="terminal-block">No pending action.</div>
|
|
79
|
+
<div id="remote-action-control-gate" class="terminal-block panel-hidden" hidden>
|
|
80
|
+
<p class="composer-warning-copy">Take control to respond from remote.</p>
|
|
81
|
+
<div class="button-row">
|
|
82
|
+
<button type="button" id="remote-action-control-button" class="command-button button-primary">Take Control</button>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<form id="remote-action-form" class="stacked-form panel-hidden" hidden>
|
|
86
|
+
<div id="remote-action-questions" class="question-list"></div>
|
|
87
|
+
<div class="button-row">
|
|
88
|
+
<button type="submit" id="remote-action-submit" class="command-button button-primary">Submit</button>
|
|
89
|
+
<button type="button" id="remote-action-cancel" class="command-button">Cancel</button>
|
|
90
|
+
</div>
|
|
91
|
+
</form>
|
|
92
|
+
<div id="remote-action-buttons" class="button-row">
|
|
93
|
+
<button type="button" id="approve-button" class="command-button button-primary">Approve</button>
|
|
94
|
+
<button type="button" id="approve-session-button" class="command-button panel-hidden" hidden>Approve for session</button>
|
|
95
|
+
<button type="button" id="decline-button" class="command-button button-danger">Decline</button>
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<section class="terminal-panel panel-emphasis composer-shell panel-hidden" id="composer-shell" hidden>
|
|
100
|
+
<div class="section-head">
|
|
101
|
+
<div>
|
|
102
|
+
<p class="section-kicker">Reply</p>
|
|
103
|
+
<h2>Reply</h2>
|
|
104
|
+
</div>
|
|
105
|
+
<span id="composer-target" class="inline-status">No live target</span>
|
|
106
|
+
</div>
|
|
107
|
+
<p id="composer-scope-note" class="sync-note">Pick a thread first.</p>
|
|
108
|
+
<form id="composer-form" class="stacked-form panel-hidden" hidden>
|
|
109
|
+
<textarea id="reply-text" rows="3" placeholder="Append to the selected live Codex thread"></textarea>
|
|
110
|
+
<div class="attachment-row">
|
|
111
|
+
<div id="dictation-indicator" class="dictation-indicator panel-hidden" hidden aria-live="polite">
|
|
112
|
+
<div class="dictation-wave" aria-hidden="true">
|
|
113
|
+
<span></span>
|
|
114
|
+
<span></span>
|
|
115
|
+
<span></span>
|
|
116
|
+
<span></span>
|
|
117
|
+
<span></span>
|
|
118
|
+
</div>
|
|
119
|
+
<span id="dictation-indicator-text" class="dictation-indicator-text">Hold to talk</span>
|
|
120
|
+
</div>
|
|
121
|
+
<div id="attachment-list" class="attachment-list panel-hidden" hidden></div>
|
|
122
|
+
</div>
|
|
123
|
+
<section id="composer-queue-shell" class="queue-shell panel-hidden" hidden>
|
|
124
|
+
<div class="queue-shell-head">
|
|
125
|
+
<p class="queue-shell-label">Queue</p>
|
|
126
|
+
<button type="button" id="clear-queue-button" class="command-button command-button-subtle command-button-compact">Clear</button>
|
|
127
|
+
</div>
|
|
128
|
+
<div id="composer-queue-list" class="queue-list"></div>
|
|
129
|
+
</section>
|
|
130
|
+
<div class="button-row composer-command-row">
|
|
131
|
+
<button type="button" id="composer-control-button" class="command-button panel-hidden" hidden>Take Control</button>
|
|
132
|
+
<button type="submit" id="send-reply-button" class="command-button button-primary">Steer Now</button>
|
|
133
|
+
<button type="button" id="queue-reply-button" class="command-button">Queue</button>
|
|
134
|
+
<button type="button" id="dictation-button" class="command-button attachment-picker dictation-button">
|
|
135
|
+
<span class="dictation-button-copy">
|
|
136
|
+
<span id="dictation-button-label" class="dictation-button-label">Dictate</span>
|
|
137
|
+
</span>
|
|
138
|
+
</button>
|
|
139
|
+
<label class="command-button attachment-picker" for="reply-image-input">Add Image</label>
|
|
140
|
+
<input id="reply-image-input" class="attachment-input" type="file" accept="image/*" multiple>
|
|
141
|
+
<button type="button" id="composer-close-button" class="command-button">Close</button>
|
|
142
|
+
</div>
|
|
143
|
+
</form>
|
|
144
|
+
<p id="composer-status" class="composer-status">Ready</p>
|
|
145
|
+
<p id="composer-sync-note" class="sync-note panel-hidden" hidden></p>
|
|
146
|
+
</section>
|
|
147
|
+
|
|
148
|
+
<section class="terminal-panel remote-feed-panel">
|
|
149
|
+
<div class="section-head feed-head">
|
|
150
|
+
<div class="feed-tools">
|
|
151
|
+
<div class="filter-group" aria-label="Feed filters">
|
|
152
|
+
<button type="button" class="filter-button is-active" data-filter="updates">Updates</button>
|
|
153
|
+
<button type="button" class="filter-button is-active" data-filter="thread">Thread</button>
|
|
154
|
+
<button type="button" class="filter-button is-active" data-filter="tools">Tools</button>
|
|
155
|
+
<button type="button" class="filter-button is-active" data-filter="changes">Changes</button>
|
|
156
|
+
</div>
|
|
157
|
+
<button type="button" id="expand-all-button" class="command-button command-button-subtle command-button-compact">Expand all</button>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="feed-list" id="remote-feed"></div>
|
|
161
|
+
</section>
|
|
162
|
+
</section>
|
|
163
|
+
</section>
|
|
164
|
+
</main>
|
|
165
|
+
<script type="module" src="/remote.js"></script>
|
|
166
|
+
</body>
|
|
167
|
+
</html>
|