@knpkv/herdr-connect 0.3.0 → 0.4.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/README.md +2 -0
- package/dist/client.d.ts +986 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +353 -54
- package/dist/client.js.map +1 -1
- package/dist/directory.d.ts +7 -1
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +1 -1
- package/dist/directory.js.map +1 -1
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +3 -2
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/terminal-release.d.ts.map +1 -1
- package/dist/internal/terminal-release.js +3 -1
- package/dist/internal/terminal-release.js.map +1 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +3 -2
- package/dist/model.js.map +1 -1
- package/dist/terminal-dimensions.d.ts +25 -0
- package/dist/terminal-dimensions.d.ts.map +1 -0
- package/dist/terminal-dimensions.js +19 -0
- package/dist/terminal-dimensions.js.map +1 -0
- package/dist/terminal-keyboard.d.ts +52 -0
- package/dist/terminal-keyboard.d.ts.map +1 -0
- package/dist/terminal-keyboard.js +141 -0
- package/dist/terminal-keyboard.js.map +1 -0
- package/dist/terminal-output.d.ts +30 -0
- package/dist/terminal-output.d.ts.map +1 -0
- package/dist/terminal-output.js +54 -0
- package/dist/terminal-output.js.map +1 -0
- package/dist/terminal-rail-navigation.d.ts +4 -0
- package/dist/terminal-rail-navigation.d.ts.map +1 -0
- package/dist/terminal-rail-navigation.js +13 -0
- package/dist/terminal-rail-navigation.js.map +1 -0
- package/dist/terminal-viewport.d.ts +32 -2
- package/dist/terminal-viewport.d.ts.map +1 -1
- package/dist/terminal-viewport.js +131 -12
- package/dist/terminal-viewport.js.map +1 -1
- package/dist/terminal-worker-guard.d.ts +7 -0
- package/dist/terminal-worker-guard.d.ts.map +1 -0
- package/dist/terminal-worker-guard.js +11 -0
- package/dist/terminal-worker-guard.js.map +1 -0
- package/dist/terminal.d.ts +1667 -25
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +1 -1
- package/dist/terminal.js.map +1 -1
- package/dist/view.d.ts +15 -2
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +46 -2
- package/dist/view.js.map +1 -1
- package/dist/work-goal-link-view.d.ts +8 -0
- package/dist/work-goal-link-view.d.ts.map +1 -0
- package/dist/work-goal-link-view.js +3 -0
- package/dist/work-goal-link-view.js.map +1 -0
- package/dist/work-goal-link.d.ts +24 -0
- package/dist/work-goal-link.d.ts.map +1 -0
- package/dist/work-goal-link.js +34 -0
- package/dist/work-goal-link.js.map +1 -0
- package/dist/work-poll.d.ts +6 -0
- package/dist/work-poll.d.ts.map +1 -0
- package/dist/work-poll.js +7 -0
- package/dist/work-poll.js.map +1 -0
- package/dist/workspace-focus.d.ts +23 -0
- package/dist/workspace-focus.d.ts.map +1 -0
- package/dist/workspace-focus.js +69 -0
- package/dist/workspace-focus.js.map +1 -0
- package/package.json +17 -15
- package/src/styles.css +139 -12
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAmC,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvE,OAAO,KAAK,WAAW,MAAM,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -4,21 +4,30 @@ import { BrowserHttpClient } from "@effect/platform-browser";
|
|
|
4
4
|
import { StateLabel, Surface, Text } from "@knpkv/rly/primitives";
|
|
5
5
|
import { decodeBoundedResponseJson } from "@knpkv/herdr-fleet/response";
|
|
6
6
|
import { Cause, Effect, Fiber, Result, Schedule, Schema } from "effect";
|
|
7
|
-
import * as AsyncResult from "effect/
|
|
8
|
-
import * as Atom from "effect/
|
|
9
|
-
import * as HttpClient from "effect/
|
|
7
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
8
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
9
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
10
10
|
import { FitAddon, init, Terminal } from "ghostty-web";
|
|
11
|
-
import { useEffect, useRef } from "react";
|
|
11
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
12
12
|
import { buildConnectForest } from "./forest.js";
|
|
13
13
|
import { applyTerminalInputIdentity } from "./terminal-input-identity.js";
|
|
14
|
+
import { clampTerminalDimensions, terminalResizeCommand } from "./terminal-dimensions.js";
|
|
14
15
|
import { FleetConnectAgentPage, FleetConnectAgents, TerminalServerSignal } from "./model.js";
|
|
15
16
|
import { makePendingTerminalInput, makeTouchScrollGesture, pageScrollCommand, wheelScrollCommand } from "./terminal-input.js";
|
|
16
|
-
import {
|
|
17
|
+
import { makeTerminalInputHandler, makeTerminalOutputBoundary, writeTerminalOutput } from "./terminal-output.js";
|
|
18
|
+
import { AgentDirectory, connectAgentKey, ConnectWorkspace, TerminalKeyRail } from "./view.js";
|
|
17
19
|
import { acquireTerminalSetup, ConnectTerminalSetupError } from "./terminal-setup.js";
|
|
18
20
|
import { terminalBackground } from "./terminal-theme.js";
|
|
19
|
-
import { bindTerminalViewport } from "./terminal-viewport.js";
|
|
21
|
+
import { bindTerminalDocumentLock, bindTerminalViewport, terminalViewportBindingActive } from "./terminal-viewport.js";
|
|
20
22
|
import { resolveConnectPreferenceDecision } from "./target.js";
|
|
21
23
|
import { nextConnectAgentIndex } from "./keyboard.js";
|
|
24
|
+
import { applyTerminalModifierToInput, dispatchTerminalKey, toggleTerminalModifier } from "./terminal-keyboard.js";
|
|
25
|
+
import { WorkSnapshots } from "@knpkv/herdr-work/model";
|
|
26
|
+
import { ConnectAgentIdentity } from "./work-goal-link-view.js";
|
|
27
|
+
import { resolveConnectWorkGoal, workSnapshotForAssociation } from "./work-goal-link.js";
|
|
28
|
+
import { WorkPollMount } from "./work-poll.js";
|
|
29
|
+
import { makeTerminalWorkerGuard } from "./terminal-worker-guard.js";
|
|
30
|
+
import { enterTerminalWorkspaceWithLock, returnToDirectoryWorkspace } from "./workspace-focus.js";
|
|
22
31
|
class ConnectNetworkError extends Schema.TaggedError()("ConnectNetworkError", {
|
|
23
32
|
detail: Schema.String
|
|
24
33
|
}) {
|
|
@@ -100,9 +109,23 @@ const loadAgents = Effect.gen(function* () {
|
|
|
100
109
|
})));
|
|
101
110
|
return directory;
|
|
102
111
|
});
|
|
112
|
+
const loadWork = Effect.gen(function* () {
|
|
113
|
+
const client = yield* HttpClient.HttpClient;
|
|
114
|
+
const response = yield* client
|
|
115
|
+
.get("/v1/work")
|
|
116
|
+
.pipe(Effect.mapError((cause) => new ConnectNetworkError({ detail: String(cause) })));
|
|
117
|
+
if (response.status < 200 || response.status >= 300) {
|
|
118
|
+
return yield* new ConnectStatusError({ status: response.status });
|
|
119
|
+
}
|
|
120
|
+
return yield* decodeBoundedResponseJson(response, WorkSnapshots).pipe(Effect.mapError((cause) => new ConnectProtocolError({
|
|
121
|
+
detail: "invalid Work snapshot",
|
|
122
|
+
cause
|
|
123
|
+
})));
|
|
124
|
+
});
|
|
103
125
|
const browserRuntime = Atom.runtime(BrowserHttpClient.layerFetch);
|
|
104
126
|
export const makeConnectAtoms = () => {
|
|
105
127
|
const agents = browserRuntime.atom(loadAgents);
|
|
128
|
+
const work = browserRuntime.atom(loadWork);
|
|
106
129
|
return {
|
|
107
130
|
activityFilter: Atom.make("all"),
|
|
108
131
|
agents,
|
|
@@ -113,10 +136,13 @@ export const makeConnectAtoms = () => {
|
|
|
113
136
|
preference: Atom.make(loadRememberedAgent),
|
|
114
137
|
preferenceError: Atom.make(null),
|
|
115
138
|
query: Atom.make(""),
|
|
116
|
-
selectedKey: Atom.make(null)
|
|
139
|
+
selectedKey: Atom.make(null),
|
|
140
|
+
work,
|
|
141
|
+
workPoll: browserRuntime.atom(Atom.refresh(work).pipe(Effect.repeat(Schedule.spaced("5 seconds"))))
|
|
117
142
|
};
|
|
118
143
|
};
|
|
119
|
-
const socketUrl = (agent,
|
|
144
|
+
const socketUrl = (agent, dimensions) => {
|
|
145
|
+
const { cols, rows } = clampTerminalDimensions(dimensions);
|
|
120
146
|
const url = new URL("/v1/connect/session", window.location.href);
|
|
121
147
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
122
148
|
url.searchParams.set("host", agent.host);
|
|
@@ -125,7 +151,18 @@ const socketUrl = (agent, cols, rows) => {
|
|
|
125
151
|
url.searchParams.set("rows", String(rows));
|
|
126
152
|
return url.toString();
|
|
127
153
|
};
|
|
128
|
-
const
|
|
154
|
+
const renderTerminalOutput = (terminal, data, outputBoundary, onError) => {
|
|
155
|
+
Effect.runFork(Effect.try({
|
|
156
|
+
try: () => {
|
|
157
|
+
writeTerminalOutput(terminal, data, outputBoundary);
|
|
158
|
+
},
|
|
159
|
+
catch: (cause) => new ConnectProtocolError({
|
|
160
|
+
detail: "terminal output could not be rendered",
|
|
161
|
+
cause
|
|
162
|
+
})
|
|
163
|
+
}).pipe(Effect.catch((error) => Effect.sync(() => onError(error)))));
|
|
164
|
+
};
|
|
165
|
+
const terminalWorker = (container, agent, update, keyboard) => Effect.scoped(Effect.gen(function* () {
|
|
129
166
|
update({ _tag: "connecting", agent });
|
|
130
167
|
yield* Effect.tryPromise({
|
|
131
168
|
try: init,
|
|
@@ -167,42 +204,62 @@ const terminalWorker = (container, agent, update) => Effect.scoped(Effect.gen(fu
|
|
|
167
204
|
});
|
|
168
205
|
}
|
|
169
206
|
applyTerminalInputIdentity(textarea);
|
|
207
|
+
const releaseTerminalFocus = keyboard.setTerminalFocus(textarea, () => terminal.terminal.focus());
|
|
208
|
+
yield* Effect.addFinalizer(() => Effect.sync(releaseTerminalFocus));
|
|
170
209
|
let ready = false;
|
|
171
210
|
let socket = null;
|
|
172
211
|
let inputOverflow = false;
|
|
212
|
+
const outputBoundary = makeTerminalOutputBoundary();
|
|
173
213
|
let pendingResize = null;
|
|
174
214
|
const pendingInput = makePendingTerminalInput();
|
|
175
215
|
const send = (command) => {
|
|
176
216
|
if (socket?.readyState === WebSocket.OPEN) {
|
|
177
217
|
socket.send(JSON.stringify(command));
|
|
218
|
+
return true;
|
|
178
219
|
}
|
|
220
|
+
return false;
|
|
179
221
|
};
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
222
|
+
const sendInput = (text) => send({ type: "terminal.input", text });
|
|
223
|
+
const releaseInputSender = keyboard.setInputSender((command) => send(command));
|
|
224
|
+
yield* Effect.addFinalizer(() => Effect.sync(releaseInputSender));
|
|
225
|
+
const releaseCursorModeReader = keyboard.setCursorModeReader(() => terminal.terminal.getMode(1) ? "application" : "normal");
|
|
226
|
+
yield* Effect.addFinalizer(() => Effect.sync(releaseCursorModeReader));
|
|
227
|
+
const applyInput = (text) => {
|
|
228
|
+
const application = applyTerminalModifierToInput(keyboard.getModifier(), text);
|
|
229
|
+
if (application._tag === "unsupported") {
|
|
230
|
+
keyboard.reportError(application);
|
|
231
|
+
return null;
|
|
183
232
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
233
|
+
return application;
|
|
234
|
+
};
|
|
235
|
+
const input = terminal.terminal.onData(makeTerminalInputHandler({
|
|
236
|
+
applyInput,
|
|
237
|
+
isReady: () => ready,
|
|
238
|
+
onFailure: (failure) => {
|
|
239
|
+
if (failure === "input_queue_overflow") {
|
|
240
|
+
inputOverflow = true;
|
|
241
|
+
update({
|
|
242
|
+
_tag: "failed",
|
|
243
|
+
agent,
|
|
244
|
+
detail: "terminal input queue exceeded 64 KiB before ready"
|
|
245
|
+
});
|
|
246
|
+
socket?.close(4429, "terminal input queue limit reached");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
update({ _tag: "failed", agent, detail: "terminal input could not be sent" });
|
|
250
|
+
socket?.close(4429, "terminal input unavailable");
|
|
251
|
+
},
|
|
252
|
+
outputBoundary,
|
|
253
|
+
pendingInput,
|
|
254
|
+
sendInput,
|
|
255
|
+
setModifier: keyboard.setModifier
|
|
256
|
+
}));
|
|
194
257
|
const resize = terminal.terminal.onResize(({ cols, rows }) => {
|
|
195
258
|
if (!ready) {
|
|
196
259
|
pendingResize = { cols, rows };
|
|
197
260
|
return;
|
|
198
261
|
}
|
|
199
|
-
send({
|
|
200
|
-
type: "terminal.resize",
|
|
201
|
-
cols,
|
|
202
|
-
rows,
|
|
203
|
-
cell_width_px: 0,
|
|
204
|
-
cell_height_px: 0
|
|
205
|
-
});
|
|
262
|
+
send(terminalResizeCommand({ cols, rows }));
|
|
206
263
|
});
|
|
207
264
|
terminal.terminal.attachCustomWheelEventHandler((event) => {
|
|
208
265
|
const command = wheelScrollCommand(event, terminal.terminal.rows);
|
|
@@ -263,7 +320,7 @@ const terminalWorker = (container, agent, update) => Effect.scoped(Effect.gen(fu
|
|
|
263
320
|
container.removeEventListener("touchcancel", touchCancel);
|
|
264
321
|
}));
|
|
265
322
|
const connectedSocket = yield* Effect.acquireRelease(Effect.callback((resume) => {
|
|
266
|
-
const value = new WebSocket(socketUrl(agent, terminal.terminal.cols, terminal.terminal.rows));
|
|
323
|
+
const value = new WebSocket(socketUrl(agent, { cols: terminal.terminal.cols, rows: terminal.terminal.rows }));
|
|
267
324
|
value.binaryType = "arraybuffer";
|
|
268
325
|
let settled = false;
|
|
269
326
|
value.addEventListener("open", () => {
|
|
@@ -292,7 +349,10 @@ const terminalWorker = (container, agent, update) => Effect.scoped(Effect.gen(fu
|
|
|
292
349
|
const message = (event) => {
|
|
293
350
|
const binary = Schema.decodeUnknownResult(Schema.instanceOf(ArrayBuffer))(event.data);
|
|
294
351
|
if (Result.isSuccess(binary)) {
|
|
295
|
-
terminal.terminal
|
|
352
|
+
renderTerminalOutput(terminal.terminal, new Uint8Array(binary.success), outputBoundary, (error) => {
|
|
353
|
+
update({ _tag: "failed", agent, detail: error.detail });
|
|
354
|
+
connectedSocket.close(4400, "terminal output could not be rendered");
|
|
355
|
+
});
|
|
296
356
|
return;
|
|
297
357
|
}
|
|
298
358
|
const decoded = Schema.decodeUnknownResult(Schema.fromJsonString(TerminalServerSignal))(event.data);
|
|
@@ -309,21 +369,16 @@ const terminalWorker = (container, agent, update) => Effect.scoped(Effect.gen(fu
|
|
|
309
369
|
ready = true;
|
|
310
370
|
const queued = pendingInput.drain();
|
|
311
371
|
if (queued.length > 0) {
|
|
312
|
-
|
|
372
|
+
if (!sendInput(queued)) {
|
|
373
|
+
update({ _tag: "failed", agent, detail: "terminal input could not be sent" });
|
|
374
|
+
connectedSocket.close(4429, "terminal input unavailable");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
313
377
|
}
|
|
314
378
|
if (pendingResize !== null) {
|
|
315
|
-
send(
|
|
316
|
-
type: "terminal.resize",
|
|
317
|
-
cols: pendingResize.cols,
|
|
318
|
-
rows: pendingResize.rows,
|
|
319
|
-
cell_width_px: 0,
|
|
320
|
-
cell_height_px: 0
|
|
321
|
-
});
|
|
379
|
+
send(terminalResizeCommand(pendingResize));
|
|
322
380
|
pendingResize = null;
|
|
323
381
|
}
|
|
324
|
-
if (window.matchMedia("(pointer: fine)").matches) {
|
|
325
|
-
terminal.terminal.focus();
|
|
326
|
-
}
|
|
327
382
|
update({ _tag: "connected", agent });
|
|
328
383
|
}
|
|
329
384
|
};
|
|
@@ -355,28 +410,206 @@ export const ConnectSurface = ({ atoms, embedded = false, roomFooter }) => {
|
|
|
355
410
|
const [preferenceError, setPreferenceError] = useAtom(atoms.preferenceError);
|
|
356
411
|
const [query, setQuery] = useAtom(atoms.query);
|
|
357
412
|
const [selectedKey, setSelectedKey] = useAtom(atoms.selectedKey);
|
|
413
|
+
const work = useAtomValue(atoms.work);
|
|
358
414
|
const preferenceApplied = useRef(false);
|
|
359
|
-
const requestId = useRef(0);
|
|
415
|
+
const requestId = useRef(connectionRequest?.id ?? 0);
|
|
416
|
+
const directorySearchRef = useRef(null);
|
|
417
|
+
const directoryViewportRef = useRef(null);
|
|
418
|
+
const shellRef = useRef(null);
|
|
419
|
+
const [shellElement, setShellElement] = useState(null);
|
|
420
|
+
const terminalActiveRequestRef = useRef(null);
|
|
421
|
+
const terminalEntryLockRef = useRef(null);
|
|
422
|
+
const terminalBackRef = useRef(null);
|
|
360
423
|
const terminalRef = useRef(null);
|
|
424
|
+
const terminalFocusTargetRef = useRef(null);
|
|
361
425
|
const terminalViewportRef = useRef(null);
|
|
426
|
+
const workspaceRef = useRef(null);
|
|
427
|
+
const terminalInputRef = useRef(() => false);
|
|
428
|
+
const terminalInputOwnerRef = useRef(null);
|
|
429
|
+
const terminalFocusRef = useRef(() => { });
|
|
430
|
+
const terminalCursorModeReaderRef = useRef(() => "normal");
|
|
431
|
+
const terminalCursorModeOwnerRef = useRef(null);
|
|
432
|
+
const terminalModifierRef = useRef(null);
|
|
433
|
+
const [terminalModifier, setTerminalModifier] = useState(null);
|
|
434
|
+
const [terminalKeyError, setTerminalKeyError] = useState(null);
|
|
435
|
+
const [workspaceFocusFailure, setWorkspaceFocusFailure] = useState(null);
|
|
362
436
|
useAtomMount(atoms.agentsPoll);
|
|
437
|
+
const releaseTerminalEntryLock = useCallback(() => {
|
|
438
|
+
const release = terminalEntryLockRef.current;
|
|
439
|
+
terminalEntryLockRef.current = null;
|
|
440
|
+
release?.();
|
|
441
|
+
}, []);
|
|
442
|
+
const attachShell = useCallback((element) => {
|
|
443
|
+
shellRef.current = element;
|
|
444
|
+
setShellElement(element);
|
|
445
|
+
}, []);
|
|
446
|
+
const workspaceElements = () => {
|
|
447
|
+
const directoryScreen = directoryViewportRef.current;
|
|
448
|
+
const terminalScreen = terminalViewportRef.current;
|
|
449
|
+
const workspace = workspaceRef.current;
|
|
450
|
+
return directoryScreen === null || terminalScreen === null || workspace === null
|
|
451
|
+
? null
|
|
452
|
+
: { directory: directoryScreen, terminal: terminalScreen, workspace };
|
|
453
|
+
};
|
|
454
|
+
const directoryFocusTarget = (agent) => {
|
|
455
|
+
const directoryScreen = directoryViewportRef.current;
|
|
456
|
+
if (directoryScreen === null)
|
|
457
|
+
return null;
|
|
458
|
+
const key = connectAgentKey(agent);
|
|
459
|
+
return ([...directoryScreen.querySelectorAll(".connect-agent")].find((button) => button.dataset.agentKey === key) ??
|
|
460
|
+
directorySearchRef.current ??
|
|
461
|
+
directoryScreen);
|
|
462
|
+
};
|
|
463
|
+
const restoreDirectoryFocus = (agent) => {
|
|
464
|
+
const elements = workspaceElements();
|
|
465
|
+
const focusTarget = directoryFocusTarget(agent);
|
|
466
|
+
if (elements !== null && focusTarget !== null)
|
|
467
|
+
return returnToDirectoryWorkspace(elements, focusTarget);
|
|
468
|
+
const terminalScreen = terminalViewportRef.current;
|
|
469
|
+
const activeElement = Schema.decodeUnknownResult(Schema.instanceOf(HTMLElement))(window.document.activeElement);
|
|
470
|
+
if (terminalScreen !== null && Result.isSuccess(activeElement) && terminalScreen.contains(activeElement.success)) {
|
|
471
|
+
activeElement.success.blur();
|
|
472
|
+
}
|
|
473
|
+
return { _tag: "failed", reason: "detached_element" };
|
|
474
|
+
};
|
|
363
475
|
useEffect(() => {
|
|
364
476
|
const container = terminalRef.current;
|
|
365
477
|
if (connectionRequest === null || container === null)
|
|
366
478
|
return;
|
|
367
|
-
const
|
|
479
|
+
const terminalRequestId = connectionRequest.id;
|
|
480
|
+
const workerGuard = makeTerminalWorkerGuard(terminalRequestId);
|
|
481
|
+
const releaseTerminalFocus = () => {
|
|
482
|
+
workerGuard.release();
|
|
483
|
+
terminalFocusRef.current = () => { };
|
|
484
|
+
terminalFocusTargetRef.current = null;
|
|
485
|
+
terminalActiveRequestRef.current = null;
|
|
486
|
+
};
|
|
487
|
+
const invalidateTerminalRequest = () => {
|
|
488
|
+
if (requestId.current === terminalRequestId)
|
|
489
|
+
requestId.current += 1;
|
|
490
|
+
setConnectionRequest(null);
|
|
491
|
+
};
|
|
492
|
+
const fiber = Effect.runFork(terminalWorker(container, connectionRequest.agent, (state) => {
|
|
493
|
+
if (!workerGuard.accepts(requestId.current))
|
|
494
|
+
return;
|
|
495
|
+
if (state._tag === "connected") {
|
|
496
|
+
const elements = workspaceElements();
|
|
497
|
+
const focusTarget = window.matchMedia("(pointer: fine)").matches
|
|
498
|
+
? terminalFocusTargetRef.current
|
|
499
|
+
: terminalBackRef.current;
|
|
500
|
+
if (elements === null || focusTarget === null) {
|
|
501
|
+
releaseTerminalFocus();
|
|
502
|
+
invalidateTerminalRequest();
|
|
503
|
+
setConnection({
|
|
504
|
+
_tag: "failed",
|
|
505
|
+
agent: state.agent,
|
|
506
|
+
detail: "terminal focus transition failed: detached_element"
|
|
507
|
+
});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
releaseTerminalEntryLock();
|
|
511
|
+
const lockedTransition = enterTerminalWorkspaceWithLock(elements, focusTarget, () => bindTerminalDocumentLock(window));
|
|
512
|
+
terminalEntryLockRef.current = lockedTransition.releaseLock;
|
|
513
|
+
const transition = lockedTransition.transition;
|
|
514
|
+
if (transition._tag === "failed") {
|
|
515
|
+
releaseTerminalEntryLock();
|
|
516
|
+
releaseTerminalFocus();
|
|
517
|
+
invalidateTerminalRequest();
|
|
518
|
+
setConnection({
|
|
519
|
+
_tag: "failed",
|
|
520
|
+
agent: state.agent,
|
|
521
|
+
detail: `terminal focus transition failed: ${transition.reason}`
|
|
522
|
+
});
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
setWorkspaceFocusFailure(null);
|
|
526
|
+
terminalActiveRequestRef.current = terminalRequestId;
|
|
527
|
+
}
|
|
528
|
+
else if (state._tag === "closed" || state._tag === "failed") {
|
|
529
|
+
if (terminalActiveRequestRef.current === terminalRequestId) {
|
|
530
|
+
const transition = restoreDirectoryFocus(state.agent);
|
|
531
|
+
if (transition._tag === "failed") {
|
|
532
|
+
setWorkspaceFocusFailure(transition.reason);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
setWorkspaceFocusFailure(null);
|
|
536
|
+
}
|
|
537
|
+
terminalActiveRequestRef.current = null;
|
|
538
|
+
}
|
|
539
|
+
invalidateTerminalRequest();
|
|
540
|
+
}
|
|
541
|
+
setConnection(state);
|
|
542
|
+
}, {
|
|
543
|
+
getModifier: () => terminalModifierRef.current,
|
|
544
|
+
setTerminalFocus: (target, focus) => {
|
|
545
|
+
terminalFocusTargetRef.current = target;
|
|
546
|
+
terminalFocusRef.current = focus;
|
|
547
|
+
return () => {
|
|
548
|
+
if (terminalFocusRef.current === focus)
|
|
549
|
+
terminalFocusRef.current = () => { };
|
|
550
|
+
if (terminalFocusTargetRef.current === target)
|
|
551
|
+
terminalFocusTargetRef.current = null;
|
|
552
|
+
};
|
|
553
|
+
},
|
|
554
|
+
reportError: () => setTerminalKeyError("That modifier combination is not supported."),
|
|
555
|
+
setInputSender: (sendInput) => {
|
|
556
|
+
const owner = Symbol("terminal-input-sender");
|
|
557
|
+
terminalInputOwnerRef.current = owner;
|
|
558
|
+
terminalInputRef.current = sendInput;
|
|
559
|
+
return () => {
|
|
560
|
+
if (terminalInputOwnerRef.current !== owner)
|
|
561
|
+
return;
|
|
562
|
+
terminalInputOwnerRef.current = null;
|
|
563
|
+
terminalInputRef.current = () => false;
|
|
564
|
+
};
|
|
565
|
+
},
|
|
566
|
+
setCursorModeReader: (read) => {
|
|
567
|
+
const owner = Symbol("terminal-cursor-mode-reader");
|
|
568
|
+
terminalCursorModeOwnerRef.current = owner;
|
|
569
|
+
terminalCursorModeReaderRef.current = read;
|
|
570
|
+
return () => {
|
|
571
|
+
if (terminalCursorModeOwnerRef.current !== owner)
|
|
572
|
+
return;
|
|
573
|
+
terminalCursorModeOwnerRef.current = null;
|
|
574
|
+
terminalCursorModeReaderRef.current = () => "normal";
|
|
575
|
+
};
|
|
576
|
+
},
|
|
577
|
+
setModifier: (modifier) => {
|
|
578
|
+
terminalModifierRef.current = modifier;
|
|
579
|
+
setTerminalModifier((current) => (current === modifier ? current : modifier));
|
|
580
|
+
setTerminalKeyError(null);
|
|
581
|
+
}
|
|
582
|
+
}));
|
|
368
583
|
return () => {
|
|
584
|
+
releaseTerminalEntryLock();
|
|
585
|
+
workerGuard.release();
|
|
369
586
|
Effect.runFork(Fiber.interrupt(fiber));
|
|
370
587
|
container.replaceChildren();
|
|
371
588
|
};
|
|
372
|
-
}, [connectionRequest, setConnection]);
|
|
373
|
-
const
|
|
374
|
-
|
|
589
|
+
}, [connectionRequest, releaseTerminalEntryLock, setConnection]);
|
|
590
|
+
const terminalVisible = connection._tag === "connected" || workspaceFocusFailure === "focus_rejected";
|
|
591
|
+
const terminalViewportActive = terminalViewportBindingActive({
|
|
592
|
+
connectionRequested: connectionRequest !== null,
|
|
593
|
+
focusRejected: workspaceFocusFailure === "focus_rejected",
|
|
594
|
+
terminalConnected: connection._tag === "connected"
|
|
595
|
+
});
|
|
596
|
+
// Size the hidden terminal before its first visible frame so Fleet navigation never overlaps it.
|
|
597
|
+
useLayoutEffect(() => {
|
|
375
598
|
const room = terminalViewportRef.current;
|
|
376
|
-
if (!
|
|
599
|
+
if (!terminalViewportActive || room === null)
|
|
377
600
|
return;
|
|
378
|
-
|
|
379
|
-
|
|
601
|
+
const attachedShell = shellElement ?? shellRef.current;
|
|
602
|
+
const topBoundary = embedded ? attachedShell : undefined;
|
|
603
|
+
if (topBoundary === null)
|
|
604
|
+
return;
|
|
605
|
+
try {
|
|
606
|
+
return bindTerminalViewport(room, window, topBoundary, terminalVisible);
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
if (terminalVisible)
|
|
610
|
+
releaseTerminalEntryLock();
|
|
611
|
+
}
|
|
612
|
+
}, [embedded, releaseTerminalEntryLock, shellElement, terminalViewportActive, terminalVisible]);
|
|
380
613
|
const current = AsyncResult.isSuccess(directory)
|
|
381
614
|
? directory.value
|
|
382
615
|
: directory._tag === "Failure" && directory.previousSuccess._tag === "Some"
|
|
@@ -387,9 +620,26 @@ export const ConnectSurface = ({ atoms, embedded = false, roomFooter }) => {
|
|
|
387
620
|
(connectionRequest !== null && connectAgentKey(connectionRequest.agent) === selectedKey
|
|
388
621
|
? connectionRequest.agent
|
|
389
622
|
: null);
|
|
623
|
+
const currentWork = workSnapshotForAssociation(work);
|
|
624
|
+
const workGoalResolution = selected === null
|
|
625
|
+
? { _tag: "unavailable", reason: "snapshot_unavailable" }
|
|
626
|
+
: currentWork === null
|
|
627
|
+
? { _tag: "unavailable", reason: "snapshot_unavailable" }
|
|
628
|
+
: resolveConnectWorkGoal(selected, currentWork);
|
|
390
629
|
const selectAgent = (agent) => {
|
|
391
630
|
preferenceApplied.current = true;
|
|
392
631
|
const key = connectAgentKey(agent);
|
|
632
|
+
terminalInputOwnerRef.current = null;
|
|
633
|
+
terminalInputRef.current = () => false;
|
|
634
|
+
terminalFocusRef.current = () => { };
|
|
635
|
+
terminalFocusTargetRef.current = null;
|
|
636
|
+
terminalCursorModeOwnerRef.current = null;
|
|
637
|
+
terminalCursorModeReaderRef.current = () => "normal";
|
|
638
|
+
terminalModifierRef.current = null;
|
|
639
|
+
terminalActiveRequestRef.current = null;
|
|
640
|
+
setTerminalModifier(null);
|
|
641
|
+
setTerminalKeyError(null);
|
|
642
|
+
setWorkspaceFocusFailure(null);
|
|
393
643
|
setSelectedKey(key);
|
|
394
644
|
setConnection({ _tag: "connecting", agent });
|
|
395
645
|
requestId.current += 1;
|
|
@@ -436,8 +686,53 @@ export const ConnectSurface = ({ atoms, embedded = false, roomFooter }) => {
|
|
|
436
686
|
next.focus();
|
|
437
687
|
};
|
|
438
688
|
const disconnect = () => {
|
|
689
|
+
let nextConnection = { _tag: "idle" };
|
|
690
|
+
let nextFocusFailure = null;
|
|
691
|
+
if (connection._tag !== "idle" && (connection._tag === "connected" || workspaceFocusFailure === "focus_rejected")) {
|
|
692
|
+
const transition = restoreDirectoryFocus(connection.agent);
|
|
693
|
+
if (transition._tag === "failed") {
|
|
694
|
+
nextConnection = {
|
|
695
|
+
_tag: "failed",
|
|
696
|
+
agent: connection.agent,
|
|
697
|
+
detail: `terminal focus transition failed: ${transition.reason}`
|
|
698
|
+
};
|
|
699
|
+
nextFocusFailure = transition.reason;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
requestId.current += 1;
|
|
703
|
+
terminalActiveRequestRef.current = null;
|
|
704
|
+
setConnection(nextConnection);
|
|
439
705
|
setConnectionRequest(null);
|
|
440
|
-
|
|
706
|
+
terminalInputOwnerRef.current = null;
|
|
707
|
+
terminalInputRef.current = () => false;
|
|
708
|
+
terminalFocusRef.current = () => { };
|
|
709
|
+
terminalFocusTargetRef.current = null;
|
|
710
|
+
terminalCursorModeOwnerRef.current = null;
|
|
711
|
+
terminalCursorModeReaderRef.current = () => "normal";
|
|
712
|
+
terminalModifierRef.current = null;
|
|
713
|
+
setTerminalModifier(null);
|
|
714
|
+
setTerminalKeyError(null);
|
|
715
|
+
setWorkspaceFocusFailure(nextFocusFailure);
|
|
716
|
+
};
|
|
717
|
+
const changeTerminalModifier = (modifier) => {
|
|
718
|
+
const next = toggleTerminalModifier(terminalModifierRef.current, modifier);
|
|
719
|
+
terminalModifierRef.current = next;
|
|
720
|
+
setTerminalModifier(next);
|
|
721
|
+
setTerminalKeyError(null);
|
|
722
|
+
};
|
|
723
|
+
const sendTerminalRailKey = (key) => {
|
|
724
|
+
const dispatch = dispatchTerminalKey(key, terminalModifierRef.current, terminalCursorModeReaderRef.current());
|
|
725
|
+
if (dispatch._tag === "unsupported") {
|
|
726
|
+
setTerminalKeyError("That modifier combination is not supported.");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (!terminalInputRef.current(dispatch.command)) {
|
|
730
|
+
setTerminalKeyError("Terminal connection is unavailable.");
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
terminalModifierRef.current = dispatch.nextModifier;
|
|
734
|
+
setTerminalModifier(dispatch.nextModifier);
|
|
735
|
+
setTerminalKeyError(null);
|
|
441
736
|
};
|
|
442
737
|
const directoryScreen = (_jsxs(_Fragment, { children: [embedded ? (_jsxs("header", { className: "connect-embedded-intro", children: [_jsxs("div", { children: [_jsx(Text, { variant: "meta", tone: "secondary", children: "Live fleet directory" }), _jsx(Text, { as: "h1", variant: "page-title", children: "Connect to an agent" }), _jsx(Text, { tone: "secondary", children: "Choose a worker, reviewer, or coordinator to open its exact terminal." })] }), _jsx(StateLabel, { label: current === null ? "Loading" : `${String(agents.length)} agents`, size: "compact", tone: current === null ? "neutral" : "positive" })] })) : (_jsxs("header", { className: "connect-header", children: [_jsxs("div", { children: [_jsx(Text, { variant: "meta", tone: "secondary", children: "Herdr fleet" }), _jsx(Text, { as: "h1", variant: "page-title", children: "Connect" })] }), _jsxs("nav", { className: "fleet-app-nav", "aria-label": "Fleet applications", children: [_jsx("a", { href: "/", children: "Approvals" }), _jsx("a", { href: "/connect/", "aria-current": "page", children: "Connect" })] })] })), _jsxs("section", { className: "connect-agents", "aria-label": "Herdr agents", onKeyDown: moveAgentFocus, children: [_jsxs("label", { className: "connect-search", children: [_jsx("span", { children: "Find agent" }), _jsx("input", { autoComplete: "off", id: "connect-agent-search", name: "agent-search", onChange: (event) => setQuery(event.currentTarget.value), onKeyDown: (event) => {
|
|
443
738
|
if (event.key === "Escape") {
|
|
@@ -455,8 +750,12 @@ export const ConnectSurface = ({ atoms, embedded = false, roomFooter }) => {
|
|
|
455
750
|
return;
|
|
456
751
|
event.preventDefault();
|
|
457
752
|
firstAgent.focus();
|
|
458
|
-
}, placeholder: "Name, host, state\u2026", type: "search", value: query })] }), current === null ? (_jsx(Text, { tone: "secondary", children: directory._tag === "Failure" ? Cause.pretty(directory.cause) : "Loading fleet agents…" })) : agents.length === 0 ? (_jsx(Text, { tone: "secondary", children: "No live agents." })) : (_jsx(AgentDirectory, { activityFilter: activityFilter, agents: agents, hostFilter: hostFilter, onActivityFilter: setActivityFilter, onHostFilter: setHostFilter, onSelect: selectAgent, query: query, selectedKey: selectedKey })), connection._tag === "connecting" ? (_jsxs("small", { className: "connect-status-message", children: ["Connecting to ", connection.agent.name, "\u2026"] })) : connection._tag === "failed" ? (_jsxs("small", { className: "connect-status-message", "data-tone": "critical", children: [connection.agent.name, " \u00B7 ", connection.detail] })) : connection._tag === "closed" ? (_jsxs("small", { className: "connect-status-message", children: [connection.agent.name, " disconnected."] })) : null, remembered._tag === "Failure" ? (_jsxs("small", { className: "connect-preference-error", children: ["Selection memory unavailable \u00B7 ", Cause.pretty(remembered.cause)] })) : preferenceError === null ? null : (_jsxs("small", { className: "connect-preference-error", children: ["Selection memory unavailable \u00B7 ", preferenceError] })), (current?.failures.length ?? 0) === 0 ? null : (_jsx("div", { className: "connect-failures", children: current?.failures.map((failure) => (_jsxs("small", { children: [failure.host, " \u00B7 ", failure.reason.replaceAll("_", " ")] }, failure.host))) }))] })] }));
|
|
459
|
-
const terminalScreen = (_jsxs(Surface, { as: "section", padding: "none", className: "terminal-stage", children: [_jsxs("div", { className: "terminal-bar", children: [_jsx("button", { className: "terminal-back", onClick: disconnect, type: "button", children: "Agents" }), _jsxs("div", { children: [_jsx("strong", { children:
|
|
460
|
-
|
|
753
|
+
}, placeholder: "Name, host, state\u2026", ref: directorySearchRef, type: "search", value: query })] }), current === null ? (_jsx(Text, { tone: "secondary", children: directory._tag === "Failure" ? Cause.pretty(directory.cause) : "Loading fleet agents…" })) : agents.length === 0 ? (_jsx(Text, { tone: "secondary", children: "No live agents." })) : (_jsx(AgentDirectory, { activityFilter: activityFilter, agents: agents, hostFilter: hostFilter, onActivityFilter: setActivityFilter, onHostFilter: setHostFilter, onSelect: selectAgent, query: query, selectedKey: selectedKey })), connection._tag === "connecting" ? (_jsxs("small", { className: "connect-status-message", children: ["Connecting to ", connection.agent.name, "\u2026"] })) : connection._tag === "failed" ? (_jsxs("small", { className: "connect-status-message", "data-tone": "critical", children: [connection.agent.name, " \u00B7 ", connection.detail] })) : connection._tag === "closed" ? (_jsxs("small", { className: "connect-status-message", children: [connection.agent.name, " disconnected."] })) : null, remembered._tag === "Failure" ? (_jsxs("small", { className: "connect-preference-error", children: ["Selection memory unavailable \u00B7 ", Cause.pretty(remembered.cause)] })) : preferenceError === null ? null : (_jsxs("small", { className: "connect-preference-error", children: ["Selection memory unavailable \u00B7 ", preferenceError] })), workspaceFocusFailure === null || workspaceFocusFailure === "focus_rejected" ? null : (_jsxs("small", { className: "connect-status-message", "data-tone": "critical", children: ["Terminal focus transition failed \u00B7 ", workspaceFocusFailure] })), (current?.failures.length ?? 0) === 0 ? null : (_jsx("div", { className: "connect-failures", children: current?.failures.map((failure) => (_jsxs("small", { children: [failure.host, " \u00B7 ", failure.reason.replaceAll("_", " ")] }, failure.host))) }))] })] }));
|
|
754
|
+
const terminalScreen = (_jsxs(Surface, { as: "section", padding: "none", className: "terminal-stage", children: [_jsxs("div", { className: "terminal-bar", children: [_jsx("button", { className: "terminal-back", onClick: disconnect, ref: terminalBackRef, type: "button", children: "Agents" }), _jsxs("div", { children: [selected === null ? (_jsx("strong", { children: "Agent" })) : (_jsx(ConnectAgentIdentity, { agent: selected, resolution: workGoalResolution })), _jsx("small", { children: selected === null ? "Herdr terminal" : `${selected.host} · ${selected.kind}` })] }), _jsx(StateLabel, { label: connection._tag === "connected"
|
|
755
|
+
? "connected"
|
|
756
|
+
: connection._tag === "closed"
|
|
757
|
+
? "disconnected"
|
|
758
|
+
: "unavailable", tone: connection._tag === "connected" ? "positive" : connection._tag === "failed" ? "critical" : "neutral", size: "compact" })] }), workspaceFocusFailure === "focus_rejected" ? (_jsxs("small", { className: "connect-status-message", "data-tone": "critical", role: "alert", children: ["Terminal focus transition failed \u00B7 ", workspaceFocusFailure] })) : null, _jsx(TerminalKeyRail, { disabled: connection._tag !== "connected", error: terminalKeyError, modifier: terminalModifier, onFocusTerminal: () => terminalFocusRef.current(), onKey: sendTerminalRailKey, onModifierChange: changeTerminalModifier }), _jsx("div", { "aria-label": selected === null ? "Agent terminal" : `${selected.name} terminal`, className: "ghostty-terminal", ref: terminalRef })] }));
|
|
759
|
+
return (_jsxs("div", { className: embedded ? "connect-shell connect-shell-embedded" : "connect-shell", ref: attachShell, tabIndex: -1, children: [_jsx(WorkPollMount, { atom: atoms.workPoll }), _jsx(ConnectWorkspace, { directory: directoryScreen, directoryViewportRef: directoryViewportRef, mode: terminalVisible ? "terminal" : "directory", terminal: terminalScreen, terminalViewportRef: terminalViewportRef, workspaceRef: workspaceRef }), roomFooter] }));
|
|
461
760
|
};
|
|
462
761
|
//# sourceMappingURL=client.js.map
|