aicodeman 1.48.0 → 1.49.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 +12 -3
- package/dist/config/cli-registry/schema.d.ts +1 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +5 -1
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +71 -5
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +4 -4
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/config/foreign-tmux.d.ts +48 -0
- package/dist/config/foreign-tmux.d.ts.map +1 -0
- package/dist/config/foreign-tmux.js +48 -0
- package/dist/config/foreign-tmux.js.map +1 -0
- package/dist/custom-model-injection-apply.d.ts +4 -2
- package/dist/custom-model-injection-apply.d.ts.map +1 -1
- package/dist/custom-model-injection-apply.js +35 -21
- package/dist/custom-model-injection-apply.js.map +1 -1
- package/dist/docker-hosts.d.ts +8 -1
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +5 -5
- package/dist/docker-hosts.js.map +1 -1
- package/dist/foreign-tmux-discovery.d.ts +140 -0
- package/dist/foreign-tmux-discovery.d.ts.map +1 -0
- package/dist/foreign-tmux-discovery.js +366 -0
- package/dist/foreign-tmux-discovery.js.map +1 -0
- package/dist/foreign-tmux.d.ts +381 -0
- package/dist/foreign-tmux.d.ts.map +1 -0
- package/dist/foreign-tmux.js +775 -0
- package/dist/foreign-tmux.js.map +1 -0
- package/dist/git-fetch-scheduler.d.ts +2 -0
- package/dist/git-fetch-scheduler.d.ts.map +1 -1
- package/dist/git-fetch-scheduler.js +1 -1
- package/dist/git-fetch-scheduler.js.map +1 -1
- package/dist/git-fetch.d.ts +1 -1
- package/dist/git-fetch.d.ts.map +1 -1
- package/dist/git-workspace-status.d.ts +2 -2
- package/dist/git-workspace-status.d.ts.map +1 -1
- package/dist/hooks-config.d.ts +2 -1
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +2 -1
- package/dist/hooks-config.js.map +1 -1
- package/dist/live-worktree.d.ts +53 -0
- package/dist/live-worktree.d.ts.map +1 -0
- package/dist/live-worktree.js +97 -0
- package/dist/live-worktree.js.map +1 -0
- package/dist/mux-interface.d.ts +12 -1
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/reboot-restore.d.ts +1 -1
- package/dist/reboot-restore.d.ts.map +1 -1
- package/dist/reboot-restore.js +9 -0
- package/dist/reboot-restore.js.map +1 -1
- package/dist/remote-files.d.ts +150 -5
- package/dist/remote-files.d.ts.map +1 -1
- package/dist/remote-files.js +505 -7
- package/dist/remote-files.js.map +1 -1
- package/dist/remote-hosts.d.ts +15 -9
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +42 -20
- package/dist/remote-hosts.js.map +1 -1
- package/dist/session-cli-registry-bridge.d.ts.map +1 -1
- package/dist/session-cli-registry-bridge.js +19 -8
- package/dist/session-cli-registry-bridge.js.map +1 -1
- package/dist/session-env-clamp.d.ts +1 -1
- package/dist/session-env-clamp.d.ts.map +1 -1
- package/dist/session-env-clamp.js +4 -3
- package/dist/session-env-clamp.js.map +1 -1
- package/dist/session.d.ts +91 -21
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +190 -33
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +74 -12
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +256 -41
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/foreign-tmux.d.ts +101 -0
- package/dist/types/foreign-tmux.d.ts.map +1 -0
- package/dist/types/foreign-tmux.js +27 -0
- package/dist/types/foreign-tmux.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/session.d.ts +106 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/types/update.d.ts +4 -1
- package/dist/types/update.d.ts.map +1 -1
- package/dist/web/edit-history-sync.d.ts +27 -7
- package/dist/web/edit-history-sync.d.ts.map +1 -1
- package/dist/web/edit-history-sync.js +35 -19
- package/dist/web/edit-history-sync.js.map +1 -1
- package/dist/web/launch-defaults.d.ts +1 -1
- package/dist/web/launch-defaults.js +2 -2
- package/dist/web/launch-defaults.js.map +1 -1
- package/dist/web/linked-cases-writer.d.ts +43 -0
- package/dist/web/linked-cases-writer.d.ts.map +1 -0
- package/dist/web/linked-cases-writer.js +107 -0
- package/dist/web/linked-cases-writer.js.map +1 -0
- package/dist/web/paste-image-gc.d.ts +4 -1
- package/dist/web/paste-image-gc.d.ts.map +1 -1
- package/dist/web/paste-image-gc.js +4 -2
- package/dist/web/paste-image-gc.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.963b5ba8.js → app.9ea37070.js} +6 -6
- package/dist/web/public/app.9ea37070.js.br +0 -0
- package/dist/web/public/app.9ea37070.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/constants.782170e1.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/edit-history-ui.js +27 -14
- package/dist/web/public/edit-history-ui.js.br +0 -0
- package/dist/web/public/edit-history-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/foreign-sessions.js +476 -0
- package/dist/web/public/foreign-sessions.js.br +0 -0
- package/dist/web/public/foreign-sessions.js.gz +0 -0
- package/dist/web/public/git-status-ui.js +5 -3
- package/dist/web/public/git-status-ui.js.br +0 -0
- package/dist/web/public/git-status-ui.js.gz +0 -0
- package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.a5e82c95.js +2 -0
- package/dist/web/public/i18n.a5e82c95.js.br +0 -0
- package/dist/web/public/i18n.a5e82c95.js.gz +0 -0
- package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
- package/dist/web/public/index.html +36 -17
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.d4ec5957.js → keyboard-accessory.b941676c.js} +3 -1
- package/dist/web/public/keyboard-accessory.b941676c.js.br +0 -0
- package/dist/web/public/{keyboard-accessory.d4ec5957.js.gz → keyboard-accessory.b941676c.js.gz} +0 -0
- package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
- package/dist/web/public/mobile-overview.js +10 -0
- package/dist/web/public/mobile-overview.js.br +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/{mobile.5ef2ce85.css → mobile.67c8eb5b.css} +1 -1
- package/dist/web/public/mobile.67c8eb5b.css.br +0 -0
- package/dist/web/public/{mobile.5ef2ce85.css.gz → mobile.67c8eb5b.css.gz} +0 -0
- package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.b100727d.js → panels-ui.8bcd4473.js} +4 -4
- package/dist/web/public/panels-ui.8bcd4473.js.br +0 -0
- package/dist/web/public/panels-ui.8bcd4473.js.gz +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.5ac82f2c.js → session-ui.a7c8c095.js} +11 -11
- package/dist/web/public/session-ui.a7c8c095.js.br +0 -0
- package/dist/web/public/session-ui.a7c8c095.js.gz +0 -0
- package/dist/web/public/{settings-ui.d1d4fb99.js → settings-ui.18f92052.js} +3 -3
- package/dist/web/public/settings-ui.18f92052.js.br +0 -0
- package/dist/web/public/settings-ui.18f92052.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
- package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
- package/dist/web/public/{styles.77dc538f.css → styles.6a718331.css} +1 -1
- package/dist/web/public/styles.6a718331.css.br +0 -0
- package/dist/web/public/{styles.77dc538f.css.gz → styles.6a718331.css.gz} +0 -0
- package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-tile.js +2 -2
- package/dist/web/public/terminal-tile.js.br +0 -0
- package/dist/web/public/terminal-tile.js.gz +0 -0
- package/dist/web/public/{terminal-ui.d7c75ce8.js → terminal-ui.a7883640.js} +2 -2
- package/dist/web/public/terminal-ui.a7883640.js.br +0 -0
- package/dist/web/public/terminal-ui.a7883640.js.gz +0 -0
- package/dist/web/public/tile-grid.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
- package/dist/web/public/vendor/fflate.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/public/whats-new.json +9 -9
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +72 -53
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/discord-routes.js +1 -1
- package/dist/web/routes/discord-routes.js.map +1 -1
- package/dist/web/routes/edit-history-routes.d.ts +2 -2
- package/dist/web/routes/edit-history-routes.d.ts.map +1 -1
- package/dist/web/routes/edit-history-routes.js +4 -3
- package/dist/web/routes/edit-history-routes.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +213 -102
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/git-status-routes.d.ts.map +1 -1
- package/dist/web/routes/git-status-routes.js +7 -2
- package/dist/web/routes/git-status-routes.js.map +1 -1
- package/dist/web/routes/mux-routes.d.ts +10 -3
- package/dist/web/routes/mux-routes.d.ts.map +1 -1
- package/dist/web/routes/mux-routes.js +59 -1
- package/dist/web/routes/mux-routes.js.map +1 -1
- package/dist/web/routes/ralph-routes.d.ts.map +1 -1
- package/dist/web/routes/ralph-routes.js +22 -0
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/readmymind-routes.d.ts.map +1 -1
- package/dist/web/routes/readmymind-routes.js +3 -2
- package/dist/web/routes/readmymind-routes.js.map +1 -1
- package/dist/web/routes/respawn-routes.d.ts.map +1 -1
- package/dist/web/routes/respawn-routes.js +24 -0
- package/dist/web/routes/respawn-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +11 -8
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +262 -29
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +3 -2
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +24 -1
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +34 -9
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/self-update.d.ts +9 -0
- package/dist/web/self-update.d.ts.map +1 -1
- package/dist/web/self-update.js +19 -3
- package/dist/web/self-update.js.map +1 -1
- package/dist/web/server.d.ts +16 -3
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +84 -24
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-wait-registry.d.ts +13 -0
- package/dist/web/session-wait-registry.d.ts.map +1 -1
- package/dist/web/session-wait-registry.js +16 -8
- package/dist/web/session-wait-registry.js.map +1 -1
- package/package.json +2 -2
- package/skills/codeman/SKILL.md +20 -4
- package/dist/web/public/app.963b5ba8.js.br +0 -0
- package/dist/web/public/app.963b5ba8.js.gz +0 -0
- package/dist/web/public/i18n.33f930d7.js +0 -2
- package/dist/web/public/i18n.33f930d7.js.br +0 -0
- package/dist/web/public/i18n.33f930d7.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.d4ec5957.js.br +0 -0
- package/dist/web/public/mobile.5ef2ce85.css.br +0 -0
- package/dist/web/public/panels-ui.b100727d.js.br +0 -0
- package/dist/web/public/panels-ui.b100727d.js.gz +0 -0
- package/dist/web/public/session-ui.5ac82f2c.js.br +0 -0
- package/dist/web/public/session-ui.5ac82f2c.js.gz +0 -0
- package/dist/web/public/settings-ui.d1d4fb99.js.br +0 -0
- package/dist/web/public/settings-ui.d1d4fb99.js.gz +0 -0
- package/dist/web/public/styles.77dc538f.css.br +0 -0
- package/dist/web/public/terminal-ui.d7c75ce8.js.br +0 -0
- package/dist/web/public/terminal-ui.d7c75ce8.js.gz +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Runs the foreign-tmux probe at the three locations Codeman
|
|
3
|
+
* already knows how to reach: this host, inside a container, and across ssh.
|
|
4
|
+
*
|
|
5
|
+
* All the judgement lives in `foreign-tmux.ts` (pure). This module is only
|
|
6
|
+
* transport + caching + the promise that it never throws.
|
|
7
|
+
*
|
|
8
|
+
* ## Cost policy
|
|
9
|
+
*
|
|
10
|
+
* The home screen shows LOCAL results automatically, so a local scan runs on a
|
|
11
|
+
* TTL cache: N browser tabs polling every few seconds still trigger at most one
|
|
12
|
+
* scan per `FOREIGN_CACHE_TTL_MS`. Docker and remote are ON DEMAND only:
|
|
13
|
+
* opening the home page must never fan out one ssh connection per saved host.
|
|
14
|
+
*
|
|
15
|
+
* ## ⚠️ The probe must not cross an extra shell
|
|
16
|
+
*
|
|
17
|
+
* The script contains `$TMUX_TMPDIR` and `$(id -u)`, which the INNER shell has to
|
|
18
|
+
* expand. Local and docker therefore spawn with an argv array, where no outer
|
|
19
|
+
* shell exists to eat them first. Remote is the exception: the ssh
|
|
20
|
+
* command line is a string like every other ssh call in this codebase, so the
|
|
21
|
+
* script is wrapped in SINGLE quotes there, which is exactly why
|
|
22
|
+
* `buildForeignProbeScript()` may not contain one.
|
|
23
|
+
*
|
|
24
|
+
* This was measured, not reasoned: running the probe through `execSync` (which
|
|
25
|
+
* spawns `sh -c`) expanded `$TMUX_TMPDIR` and `$U` in the OUTER shell, leaving the
|
|
26
|
+
* inner loop iterating over the literal `/tmux-/*` and reporting zero sessions on
|
|
27
|
+
* a machine that had three.
|
|
28
|
+
*
|
|
29
|
+
* ## A probe that goes wrong still answers
|
|
30
|
+
*
|
|
31
|
+
* All three transports go through `runForeignProbe`, which never rejects and hands
|
|
32
|
+
* back what it read. A timeout has to reach the whole process tree, not just the
|
|
33
|
+
* `sh` on top (the same rule as `runFetch` in git-fetch.ts): signalling only `sh`
|
|
34
|
+
* left its tmux child stuck on a stopped server, one more per scan, for as long as
|
|
35
|
+
* the home screen stayed open. And a scan cut short keeps the rows it already has,
|
|
36
|
+
* since one bad server must not empty the list of a machine with good ones.
|
|
37
|
+
*
|
|
38
|
+
* @module foreign-tmux-discovery
|
|
39
|
+
*/
|
|
40
|
+
import type { ForeignDiscoveryResult, ForeignTmuxSession } from './types/foreign-tmux.js';
|
|
41
|
+
import type { DockerCase, RemoteHost, SessionDocker } from './types/session.js';
|
|
42
|
+
/** What one probe produced, however it ended. */
|
|
43
|
+
export interface ForeignProbeRun {
|
|
44
|
+
/** Everything read before it finished or was stopped. */
|
|
45
|
+
stdout: string;
|
|
46
|
+
/** False when it timed out, flooded, exited non-zero or could not start. */
|
|
47
|
+
ok: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run one probe in its own process group. Never rejects (see @fileoverview).
|
|
51
|
+
*
|
|
52
|
+
* The caller is answered AT the deadline, while the group is still dying.
|
|
53
|
+
*/
|
|
54
|
+
export declare function runForeignProbe(argv: readonly string[], opts?: {
|
|
55
|
+
timeoutMs?: number;
|
|
56
|
+
maxBytes?: number;
|
|
57
|
+
}): Promise<ForeignProbeRun>;
|
|
58
|
+
/**
|
|
59
|
+
* Turn one probe's stdout into candidates. Shared by all three transports, so a
|
|
60
|
+
* container's sessions and the host's are classified by identical rules.
|
|
61
|
+
*
|
|
62
|
+
* `where` opens every note this scan writes (`local`, `docker <name>`, `remote
|
|
63
|
+
* <label>`). The browser tells the two kinds apart by it: a local note is re-read on
|
|
64
|
+
* every poll, a container or host note only by the next explicit scan.
|
|
65
|
+
*/
|
|
66
|
+
export declare function candidatesFrom(stdout: string, location: ForeignTmuxSession['location'], hostKey: string, extra: Pick<ForeignTmuxSession, 'hostId' | 'hostLabel' | 'containerName'>, where: string, notes: string[]): ForeignTmuxSession[];
|
|
67
|
+
/**
|
|
68
|
+
* Scan this host. TTL-cached and single-flight, because this is the one location
|
|
69
|
+
* the home screen polls on its own.
|
|
70
|
+
*/
|
|
71
|
+
export declare function discoverLocalForeign(force?: boolean): Promise<ForeignDiscoveryResult>;
|
|
72
|
+
/** Drop the local cache so the next scan is real (used right after an adopt). */
|
|
73
|
+
export declare function invalidateForeignCache(): void;
|
|
74
|
+
/** Connection facts for one adoptable container, resolved from the registries. */
|
|
75
|
+
export interface DockerTargetInfo {
|
|
76
|
+
hostId: string;
|
|
77
|
+
label: string;
|
|
78
|
+
containerName: string;
|
|
79
|
+
base: Pick<SessionDocker, 'engine' | 'context' | 'daemonHost'>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The argv of one in-container probe.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ `dockerEngineArgv`, never `buildDockerBaseArgs`, whose `--context` and `-H`
|
|
85
|
+
* values are shell-quoted for the launch string. `execFile` passes those quotes
|
|
86
|
+
* through literally, so `docker --context "'default'"` answered "context not found"
|
|
87
|
+
* and every container behind a named context or a remote daemon read as unreachable.
|
|
88
|
+
*/
|
|
89
|
+
export declare function foreignDockerProbeArgv(target: DockerTargetInfo): string[];
|
|
90
|
+
/**
|
|
91
|
+
* Scan the containers behind the caller's docker cases. On demand only.
|
|
92
|
+
*
|
|
93
|
+
* Read-only by construction: one `docker exec` running the probe. Never inspects
|
|
94
|
+
* lifecycle, never starts anything: a stopped or missing container simply
|
|
95
|
+
* contributes a note, mirroring `probeAdoptableContainer`'s "engine unreachable
|
|
96
|
+
* degrades to an empty list" rule.
|
|
97
|
+
*/
|
|
98
|
+
export declare function discoverDockerForeign(cases: DockerCase[]): Promise<ForeignDiscoveryResult>;
|
|
99
|
+
/**
|
|
100
|
+
* The ssh command line of one remote probe.
|
|
101
|
+
*
|
|
102
|
+
* `exec` so a timeout kill reaches ssh itself, not only the local `sh -c` around it.
|
|
103
|
+
*/
|
|
104
|
+
export declare function foreignRemoteProbeCommand(host: RemoteHost): string;
|
|
105
|
+
/**
|
|
106
|
+
* Scan saved ssh hosts. On demand only: this is the expensive one.
|
|
107
|
+
*
|
|
108
|
+
* ⚠️ The ssh command is a STRING (as every ssh call in this codebase is), so the
|
|
109
|
+
* probe crosses one extra shell and must be single-quoted. `shellescape` does
|
|
110
|
+
* that, and `buildForeignProbeScript()` is single-quote-free precisely so the
|
|
111
|
+
* escaping stays a wrap rather than a rewrite.
|
|
112
|
+
*
|
|
113
|
+
* Each host holds a slot of the shared remote-ssh limiter: one saved host per
|
|
114
|
+
* connection, all at once, is the burst OpenSSH's `MaxStartups` starts dropping.
|
|
115
|
+
* A host that waits too long for a slot reads as unreachable this round.
|
|
116
|
+
*/
|
|
117
|
+
export declare function discoverRemoteForeign(hosts: RemoteHost[]): Promise<ForeignDiscoveryResult>;
|
|
118
|
+
export interface ForeignDiscoveryRequest {
|
|
119
|
+
/** Always scanned (cheap, cached). */
|
|
120
|
+
local?: boolean;
|
|
121
|
+
/** Docker cases the CALLER may see; ownership filtering happens in the route. */
|
|
122
|
+
dockerCases?: DockerCase[];
|
|
123
|
+
/** Remote hosts the CALLER may see. */
|
|
124
|
+
remoteHosts?: RemoteHost[];
|
|
125
|
+
/** Skip the local TTL cache. */
|
|
126
|
+
force?: boolean;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* One scan across the requested locations. Merges notes, never throws.
|
|
130
|
+
*
|
|
131
|
+
* The caller supplies the docker cases and remote hosts rather than this module
|
|
132
|
+
* reading the registries itself: which of them a user may see is an ownership
|
|
133
|
+
* question, and ownership belongs to the route layer.
|
|
134
|
+
*/
|
|
135
|
+
export declare function discoverForeignSessions(req: ForeignDiscoveryRequest): Promise<ForeignDiscoveryResult>;
|
|
136
|
+
/** Re-read the caller-visible docker cases, for routes that only hold a username. */
|
|
137
|
+
export declare function readAllDockerCases(): Promise<DockerCase[]>;
|
|
138
|
+
/** Re-read the saved remote hosts. */
|
|
139
|
+
export declare function readAllRemoteHosts(): Promise<RemoteHost[]>;
|
|
140
|
+
//# sourceMappingURL=foreign-tmux-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-tmux-discovery.d.ts","sourceRoot":"","sources":["../src/foreign-tmux-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAwBH,OAAO,KAAK,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAc,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAI5F,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACnD,OAAO,CAAC,eAAe,CAAC,CAsD1B;AAOD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,kBAAkB,CAAC,UAAU,CAAC,EACxC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC,EACzE,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EAAE,GACd,kBAAkB,EAAE,CAwDtB;AASD;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA6BzF;AAED,iFAAiF;AACjF,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAMD,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC,CAAC;CAChE;AA4BD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA+BhG;AAMD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAOlE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAwBhG;AAMD,MAAM,WAAW,uBAAuB;IACtC,sCAAsC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,uCAAuC;IACvC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,gCAAgC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAW3G;AAED,qFAAqF;AACrF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAEhE;AAED,sCAAsC;AACtC,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAEhE"}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Runs the foreign-tmux probe at the three locations Codeman
|
|
3
|
+
* already knows how to reach: this host, inside a container, and across ssh.
|
|
4
|
+
*
|
|
5
|
+
* All the judgement lives in `foreign-tmux.ts` (pure). This module is only
|
|
6
|
+
* transport + caching + the promise that it never throws.
|
|
7
|
+
*
|
|
8
|
+
* ## Cost policy
|
|
9
|
+
*
|
|
10
|
+
* The home screen shows LOCAL results automatically, so a local scan runs on a
|
|
11
|
+
* TTL cache: N browser tabs polling every few seconds still trigger at most one
|
|
12
|
+
* scan per `FOREIGN_CACHE_TTL_MS`. Docker and remote are ON DEMAND only:
|
|
13
|
+
* opening the home page must never fan out one ssh connection per saved host.
|
|
14
|
+
*
|
|
15
|
+
* ## ⚠️ The probe must not cross an extra shell
|
|
16
|
+
*
|
|
17
|
+
* The script contains `$TMUX_TMPDIR` and `$(id -u)`, which the INNER shell has to
|
|
18
|
+
* expand. Local and docker therefore spawn with an argv array, where no outer
|
|
19
|
+
* shell exists to eat them first. Remote is the exception: the ssh
|
|
20
|
+
* command line is a string like every other ssh call in this codebase, so the
|
|
21
|
+
* script is wrapped in SINGLE quotes there, which is exactly why
|
|
22
|
+
* `buildForeignProbeScript()` may not contain one.
|
|
23
|
+
*
|
|
24
|
+
* This was measured, not reasoned: running the probe through `execSync` (which
|
|
25
|
+
* spawns `sh -c`) expanded `$TMUX_TMPDIR` and `$U` in the OUTER shell, leaving the
|
|
26
|
+
* inner loop iterating over the literal `/tmux-/*` and reporting zero sessions on
|
|
27
|
+
* a machine that had three.
|
|
28
|
+
*
|
|
29
|
+
* ## A probe that goes wrong still answers
|
|
30
|
+
*
|
|
31
|
+
* All three transports go through `runForeignProbe`, which never rejects and hands
|
|
32
|
+
* back what it read. A timeout has to reach the whole process tree, not just the
|
|
33
|
+
* `sh` on top (the same rule as `runFetch` in git-fetch.ts): signalling only `sh`
|
|
34
|
+
* left its tmux child stuck on a stopped server, one more per scan, for as long as
|
|
35
|
+
* the home screen stayed open. And a scan cut short keeps the rows it already has,
|
|
36
|
+
* since one bad server must not empty the list of a machine with good ones.
|
|
37
|
+
*
|
|
38
|
+
* @module foreign-tmux-discovery
|
|
39
|
+
*/
|
|
40
|
+
import { spawn } from 'node:child_process';
|
|
41
|
+
import { buildForeignProbeScript, classifyForeignPaneMode, createForeignSignatureMatcher, foreignRemoteShellCommand, foreignSessionId, isAdoptableSessionName, isAdoptableSocketPath, isCodemanOwnedPane, parseForeignProbeOutput, } from './foreign-tmux.js';
|
|
42
|
+
import { dockerEngineArgv, readDockerCases, readDockerHosts } from './docker-hosts.js';
|
|
43
|
+
import { buildSshConnectionArgs, readRemoteHosts, remoteSshTarget, shellescape } from './remote-hosts.js';
|
|
44
|
+
import { runWithRemoteSshLimit } from './remote-ssh-limiter.js';
|
|
45
|
+
import { resolveTmuxSocketName, getDataDir } from './config/instance.js';
|
|
46
|
+
import { FOREIGN_CACHE_TTL_MS, FOREIGN_PROBE_KILL_GRACE_MS, FOREIGN_PROBE_MAX_BYTES, FOREIGN_PROBE_TIMEOUT_MS, } from './config/foreign-tmux.js';
|
|
47
|
+
const IS_TEST_MODE = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
|
|
48
|
+
/**
|
|
49
|
+
* Run one probe in its own process group. Never rejects (see @fileoverview).
|
|
50
|
+
*
|
|
51
|
+
* The caller is answered AT the deadline, while the group is still dying.
|
|
52
|
+
*/
|
|
53
|
+
export function runForeignProbe(argv, opts = {}) {
|
|
54
|
+
const { timeoutMs = FOREIGN_PROBE_TIMEOUT_MS, maxBytes = FOREIGN_PROBE_MAX_BYTES } = opts;
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
const [file, ...args] = argv;
|
|
57
|
+
const chunks = [];
|
|
58
|
+
let bytes = 0;
|
|
59
|
+
let settled = false;
|
|
60
|
+
let child;
|
|
61
|
+
let escalation;
|
|
62
|
+
const finish = (ok) => {
|
|
63
|
+
if (settled)
|
|
64
|
+
return;
|
|
65
|
+
settled = true;
|
|
66
|
+
clearTimeout(deadline);
|
|
67
|
+
resolve({ stdout: Buffer.concat(chunks).toString('utf-8'), ok });
|
|
68
|
+
};
|
|
69
|
+
const signalGroup = (signal) => {
|
|
70
|
+
try {
|
|
71
|
+
if (child?.pid)
|
|
72
|
+
process.kill(-child.pid, signal);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* already gone */
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const stop = () => {
|
|
79
|
+
signalGroup('SIGTERM');
|
|
80
|
+
escalation = setTimeout(() => signalGroup('SIGKILL'), FOREIGN_PROBE_KILL_GRACE_MS);
|
|
81
|
+
escalation.unref();
|
|
82
|
+
// ⚠️ Let go of the pipe by hand. A stopped tmux server can hold it open with
|
|
83
|
+
// everything in this group dead (see foreign-tmux.ts), so waiting for its EOF
|
|
84
|
+
// would keep one handle per scan until that server is resumed.
|
|
85
|
+
child?.stdout?.destroy();
|
|
86
|
+
finish(false);
|
|
87
|
+
};
|
|
88
|
+
const deadline = setTimeout(stop, timeoutMs);
|
|
89
|
+
try {
|
|
90
|
+
child = spawn(file ?? '', args, { stdio: ['ignore', 'pipe', 'ignore'], detached: true });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
finish(false);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
child.stdout?.on('data', (chunk) => {
|
|
97
|
+
if (settled)
|
|
98
|
+
return;
|
|
99
|
+
chunks.push(chunk.subarray(0, Math.max(0, maxBytes - bytes)));
|
|
100
|
+
bytes += chunk.length;
|
|
101
|
+
if (bytes > maxBytes)
|
|
102
|
+
stop();
|
|
103
|
+
});
|
|
104
|
+
child.on('error', () => finish(false));
|
|
105
|
+
child.on('close', (code) => {
|
|
106
|
+
// The group is gone, so a late SIGKILL could only reach whoever reused its id.
|
|
107
|
+
clearTimeout(escalation);
|
|
108
|
+
finish(code === 0);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** The note for a probe that ended badly. Rows already read stay, so say which case this is. */
|
|
113
|
+
function probeFailureNote(where, run, nothing) {
|
|
114
|
+
return run.stdout ? `${where}: the scan did not finish, this list may be incomplete` : `${where}: ${nothing}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Turn one probe's stdout into candidates. Shared by all three transports, so a
|
|
118
|
+
* container's sessions and the host's are classified by identical rules.
|
|
119
|
+
*
|
|
120
|
+
* `where` opens every note this scan writes (`local`, `docker <name>`, `remote
|
|
121
|
+
* <label>`). The browser tells the two kinds apart by it: a local note is re-read on
|
|
122
|
+
* every poll, a container or host note only by the next explicit scan.
|
|
123
|
+
*/
|
|
124
|
+
export function candidatesFrom(stdout, location, hostKey, extra, where, notes) {
|
|
125
|
+
const probe = parseForeignProbeOutput(stdout);
|
|
126
|
+
const match = createForeignSignatureMatcher();
|
|
127
|
+
const ownSocket = resolveTmuxSocketName();
|
|
128
|
+
const out = [];
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
let unsafe = 0;
|
|
131
|
+
for (const pane of probe.panes) {
|
|
132
|
+
if (isCodemanOwnedPane(pane, ownSocket))
|
|
133
|
+
continue;
|
|
134
|
+
// ⚠️ THE security gate, and it lives here rather than at the adopt endpoint
|
|
135
|
+
// on purpose: a name that cannot cross the launch chain safely never gets an
|
|
136
|
+
// id, so there is nothing for a caller to send. Adoption re-resolves through
|
|
137
|
+
// this same function, which makes the endpoint fail closed for free.
|
|
138
|
+
// See `isAdoptableSessionName` for the injection this prevents.
|
|
139
|
+
if (!isAdoptableSessionName(pane.sessionName) || !isAdoptableSocketPath(pane.socketPath)) {
|
|
140
|
+
unsafe += 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// One row per SESSION, not per pane: the first pane is the one we would land
|
|
144
|
+
// on, and a multi-pane session is still a single thing to adopt.
|
|
145
|
+
// \u0000 as an ESCAPE, never the literal byte: a raw NUL in the source makes git
|
|
146
|
+
// treat this whole file as binary (no diff, no review), and the allowlists above
|
|
147
|
+
// already guarantee neither half can contain one.
|
|
148
|
+
const key = `${pane.socketPath}\u0000${pane.sessionName}`;
|
|
149
|
+
if (seen.has(key))
|
|
150
|
+
continue;
|
|
151
|
+
seen.add(key);
|
|
152
|
+
const classified = classifyForeignPaneMode(pane, probe, match);
|
|
153
|
+
out.push({
|
|
154
|
+
id: foreignSessionId(location, hostKey, pane.socketPath, pane.sessionName),
|
|
155
|
+
location,
|
|
156
|
+
...extra,
|
|
157
|
+
socketPath: pane.socketPath,
|
|
158
|
+
sessionName: pane.sessionName,
|
|
159
|
+
windows: pane.windows,
|
|
160
|
+
attached: pane.sessionAttached,
|
|
161
|
+
createdAt: pane.sessionCreated * 1000,
|
|
162
|
+
mode: classified.mode,
|
|
163
|
+
command: classified.command,
|
|
164
|
+
workingDir: pane.paneCurrentPath,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (unsafe) {
|
|
168
|
+
notes.push(`${where}: ${unsafe} session(s) skipped, the tmux session name or socket path is one Codeman cannot pass safely to a shell or to tmux`);
|
|
169
|
+
}
|
|
170
|
+
// Another Codeman's server is not a session this list would ever have shown.
|
|
171
|
+
const hung = probe.hungSockets.filter((socketPath) => !isCodemanOwnedPane({ socketPath, sessionName: '' }, ownSocket));
|
|
172
|
+
if (hung.length) {
|
|
173
|
+
notes.push(`${where}: ${hung.length} tmux server(s) did not answer, sessions on them are not listed`);
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
// ===========================================================================
|
|
178
|
+
// Local
|
|
179
|
+
// ===========================================================================
|
|
180
|
+
let localCache = null;
|
|
181
|
+
let localInFlight = null;
|
|
182
|
+
/**
|
|
183
|
+
* Scan this host. TTL-cached and single-flight, because this is the one location
|
|
184
|
+
* the home screen polls on its own.
|
|
185
|
+
*/
|
|
186
|
+
export async function discoverLocalForeign(force = false) {
|
|
187
|
+
if (IS_TEST_MODE)
|
|
188
|
+
return { sessions: [], scannedAt: Date.now(), notes: [] };
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
if (!force && localCache && now - localCache.at < FOREIGN_CACHE_TTL_MS)
|
|
191
|
+
return localCache.result;
|
|
192
|
+
if (localInFlight)
|
|
193
|
+
return localInFlight;
|
|
194
|
+
localInFlight = (async () => {
|
|
195
|
+
const notes = [];
|
|
196
|
+
let sessions = [];
|
|
197
|
+
try {
|
|
198
|
+
// argv array: no outer shell, so the script's own `$TMUX_TMPDIR`/`$(id -u)`
|
|
199
|
+
// reach the inner sh intact (see @fileoverview).
|
|
200
|
+
const run = await runForeignProbe(['sh', '-c', buildForeignProbeScript()]);
|
|
201
|
+
sessions = candidatesFrom(run.stdout, 'local', 'local', {}, 'local', notes);
|
|
202
|
+
if (!run.ok)
|
|
203
|
+
notes.push(probeFailureNote('local', run, 'tmux probe produced no result'));
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// The probe answers rather than throws, so this is the classifier. "Nothing to
|
|
207
|
+
// adopt here" is the only honest answer and it is never an error to the caller.
|
|
208
|
+
notes.push('local: tmux probe produced no result');
|
|
209
|
+
}
|
|
210
|
+
const result = { sessions, scannedAt: Date.now(), notes };
|
|
211
|
+
localCache = { at: Date.now(), result };
|
|
212
|
+
return result;
|
|
213
|
+
})().finally(() => {
|
|
214
|
+
localInFlight = null;
|
|
215
|
+
});
|
|
216
|
+
return localInFlight;
|
|
217
|
+
}
|
|
218
|
+
/** Drop the local cache so the next scan is real (used right after an adopt). */
|
|
219
|
+
export function invalidateForeignCache() {
|
|
220
|
+
localCache = null;
|
|
221
|
+
}
|
|
222
|
+
function dockerTargets(cases, hosts) {
|
|
223
|
+
const byId = new Map(hosts.map((h) => [h.id, h]));
|
|
224
|
+
const out = [];
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
for (const c of cases) {
|
|
227
|
+
const container = c.container || `codeman-case-${c.name}`;
|
|
228
|
+
// Per engine, not global: two daemons can each run a container of one name,
|
|
229
|
+
// and collapsing them here would leave the second one unscanned.
|
|
230
|
+
const key = `${c.hostId}\u0000${container}`;
|
|
231
|
+
if (seen.has(key))
|
|
232
|
+
continue;
|
|
233
|
+
seen.add(key);
|
|
234
|
+
const host = byId.get(c.hostId);
|
|
235
|
+
out.push({
|
|
236
|
+
hostId: c.hostId,
|
|
237
|
+
label: host?.label || c.name,
|
|
238
|
+
containerName: container,
|
|
239
|
+
base: {
|
|
240
|
+
engine: host?.engine ?? 'docker',
|
|
241
|
+
context: host?.context,
|
|
242
|
+
daemonHost: host?.daemonHost,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* The argv of one in-container probe.
|
|
250
|
+
*
|
|
251
|
+
* ⚠️ `dockerEngineArgv`, never `buildDockerBaseArgs`, whose `--context` and `-H`
|
|
252
|
+
* values are shell-quoted for the launch string. `execFile` passes those quotes
|
|
253
|
+
* through literally, so `docker --context "'default'"` answered "context not found"
|
|
254
|
+
* and every container behind a named context or a remote daemon read as unreachable.
|
|
255
|
+
*/
|
|
256
|
+
export function foreignDockerProbeArgv(target) {
|
|
257
|
+
return [...dockerEngineArgv(target.base), 'exec', target.containerName, 'sh', '-lc', buildForeignProbeScript()];
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Scan the containers behind the caller's docker cases. On demand only.
|
|
261
|
+
*
|
|
262
|
+
* Read-only by construction: one `docker exec` running the probe. Never inspects
|
|
263
|
+
* lifecycle, never starts anything: a stopped or missing container simply
|
|
264
|
+
* contributes a note, mirroring `probeAdoptableContainer`'s "engine unreachable
|
|
265
|
+
* degrades to an empty list" rule.
|
|
266
|
+
*/
|
|
267
|
+
export async function discoverDockerForeign(cases) {
|
|
268
|
+
if (IS_TEST_MODE)
|
|
269
|
+
return { sessions: [], scannedAt: Date.now(), notes: [] };
|
|
270
|
+
const hosts = await readDockerHosts(getDataDir()).catch(() => []);
|
|
271
|
+
const targets = dockerTargets(cases, hosts);
|
|
272
|
+
const notes = [];
|
|
273
|
+
const sessions = [];
|
|
274
|
+
await Promise.all(targets.map(async (t) => {
|
|
275
|
+
const where = `docker ${t.containerName}`;
|
|
276
|
+
try {
|
|
277
|
+
const run = await runForeignProbe(foreignDockerProbeArgv(t));
|
|
278
|
+
sessions.push(...candidatesFrom(run.stdout, 'docker', `${t.hostId} ${t.containerName}`, { hostId: t.hostId, hostLabel: t.label, containerName: t.containerName }, where, notes));
|
|
279
|
+
if (!run.ok)
|
|
280
|
+
notes.push(probeFailureNote(where, run, 'not running, no tmux, or engine unreachable'));
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
notes.push(`${where}: not running, no tmux, or engine unreachable`);
|
|
284
|
+
}
|
|
285
|
+
}));
|
|
286
|
+
return { sessions, scannedAt: Date.now(), notes };
|
|
287
|
+
}
|
|
288
|
+
// ===========================================================================
|
|
289
|
+
// Remote
|
|
290
|
+
// ===========================================================================
|
|
291
|
+
/**
|
|
292
|
+
* The ssh command line of one remote probe.
|
|
293
|
+
*
|
|
294
|
+
* `exec` so a timeout kill reaches ssh itself, not only the local `sh -c` around it.
|
|
295
|
+
*/
|
|
296
|
+
export function foreignRemoteProbeCommand(host) {
|
|
297
|
+
return [
|
|
298
|
+
'exec',
|
|
299
|
+
...buildSshConnectionArgs(host),
|
|
300
|
+
remoteSshTarget(host),
|
|
301
|
+
shellescape(foreignRemoteShellCommand(buildForeignProbeScript())),
|
|
302
|
+
].join(' ');
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Scan saved ssh hosts. On demand only: this is the expensive one.
|
|
306
|
+
*
|
|
307
|
+
* ⚠️ The ssh command is a STRING (as every ssh call in this codebase is), so the
|
|
308
|
+
* probe crosses one extra shell and must be single-quoted. `shellescape` does
|
|
309
|
+
* that, and `buildForeignProbeScript()` is single-quote-free precisely so the
|
|
310
|
+
* escaping stays a wrap rather than a rewrite.
|
|
311
|
+
*
|
|
312
|
+
* Each host holds a slot of the shared remote-ssh limiter: one saved host per
|
|
313
|
+
* connection, all at once, is the burst OpenSSH's `MaxStartups` starts dropping.
|
|
314
|
+
* A host that waits too long for a slot reads as unreachable this round.
|
|
315
|
+
*/
|
|
316
|
+
export async function discoverRemoteForeign(hosts) {
|
|
317
|
+
if (IS_TEST_MODE)
|
|
318
|
+
return { sessions: [], scannedAt: Date.now(), notes: [] };
|
|
319
|
+
const notes = [];
|
|
320
|
+
const sessions = [];
|
|
321
|
+
await Promise.all(hosts.map(async (host) => {
|
|
322
|
+
const where = `remote ${host.label}`;
|
|
323
|
+
try {
|
|
324
|
+
const run = await runWithRemoteSshLimit(() => runForeignProbe(['sh', '-c', foreignRemoteProbeCommand(host)]), {
|
|
325
|
+
maxWaitMs: FOREIGN_PROBE_TIMEOUT_MS,
|
|
326
|
+
});
|
|
327
|
+
sessions.push(...candidatesFrom(run.stdout, 'remote', host.id, { hostId: host.id, hostLabel: host.label }, where, notes));
|
|
328
|
+
if (!run.ok)
|
|
329
|
+
notes.push(probeFailureNote(where, run, 'unreachable, or no tmux'));
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
notes.push(`${where}: unreachable, or no tmux`);
|
|
333
|
+
}
|
|
334
|
+
}));
|
|
335
|
+
return { sessions, scannedAt: Date.now(), notes };
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* One scan across the requested locations. Merges notes, never throws.
|
|
339
|
+
*
|
|
340
|
+
* The caller supplies the docker cases and remote hosts rather than this module
|
|
341
|
+
* reading the registries itself: which of them a user may see is an ownership
|
|
342
|
+
* question, and ownership belongs to the route layer.
|
|
343
|
+
*/
|
|
344
|
+
export async function discoverForeignSessions(req) {
|
|
345
|
+
const parts = [];
|
|
346
|
+
if (req.local !== false)
|
|
347
|
+
parts.push(await discoverLocalForeign(req.force));
|
|
348
|
+
if (req.dockerCases?.length)
|
|
349
|
+
parts.push(await discoverDockerForeign(req.dockerCases));
|
|
350
|
+
if (req.remoteHosts?.length)
|
|
351
|
+
parts.push(await discoverRemoteForeign(req.remoteHosts));
|
|
352
|
+
return {
|
|
353
|
+
sessions: parts.flatMap((p) => p.sessions),
|
|
354
|
+
scannedAt: parts.length ? Math.max(...parts.map((p) => p.scannedAt)) : Date.now(),
|
|
355
|
+
notes: parts.flatMap((p) => p.notes),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** Re-read the caller-visible docker cases, for routes that only hold a username. */
|
|
359
|
+
export async function readAllDockerCases() {
|
|
360
|
+
return readDockerCases(getDataDir()).catch(() => []);
|
|
361
|
+
}
|
|
362
|
+
/** Re-read the saved remote hosts. */
|
|
363
|
+
export async function readAllRemoteHosts() {
|
|
364
|
+
return readRemoteHosts(getDataDir()).catch(() => []);
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=foreign-tmux-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-tmux-discovery.js","sourceRoot":"","sources":["../src/foreign-tmux-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,yBAAyB,EACzB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1G,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAIlC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC;AAUtF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAuB,EACvB,OAAkD,EAAE;IAEpD,MAAM,EAAE,SAAS,GAAG,wBAAwB,EAAE,QAAQ,GAAG,uBAAuB,EAAE,GAAG,IAAI,CAAC;IAC1F,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;QAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,KAA+B,CAAC;QACpC,IAAI,UAAsC,CAAC;QAE3C,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACnE,CAAC,CAAC;QACF,MAAM,WAAW,GAAG,CAAC,MAAsB,EAAE,EAAE;YAC7C,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,GAAG;oBAAE,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,kBAAkB;YACpB,CAAC;QACH,CAAC,CAAC;QACF,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,WAAW,CAAC,SAAS,CAAC,CAAC;YACvB,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,2BAA2B,CAAC,CAAC;YACnF,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,6EAA6E;YAC7E,8EAA8E;YAC9E,+DAA+D;YAC/D,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YACzB,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC;QACF,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAE7C,IAAI,CAAC;YACH,KAAK,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3F,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,KAAK,CAAC,CAAC;YACd,OAAO;QACT,CAAC;QACD,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACzC,IAAI,OAAO;gBAAE,OAAO;YACpB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC9D,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;YACtB,IAAI,KAAK,GAAG,QAAQ;gBAAE,IAAI,EAAE,CAAC;QAC/B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,+EAA+E;YAC/E,YAAY,CAAC,UAAU,CAAC,CAAC;YACzB,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,gGAAgG;AAChG,SAAS,gBAAgB,CAAC,KAAa,EAAE,GAAoB,EAAE,OAAe;IAC5E,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,wDAAwD,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,OAAO,EAAE,CAAC;AAChH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,QAAwC,EACxC,OAAe,EACf,KAAyE,EACzE,KAAa,EACb,KAAe;IAEf,MAAM,KAAK,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,6BAA6B,EAAE,CAAC;IAC9C,MAAM,SAAS,GAAG,qBAAqB,EAAE,CAAC;IAC1C,MAAM,GAAG,GAAyB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC;YAAE,SAAS;QAClD,4EAA4E;QAC5E,6EAA6E;QAC7E,6EAA6E;QAC7E,qEAAqE;QACrE,gEAAgE;QAChE,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACzF,MAAM,IAAI,CAAC,CAAC;YACZ,SAAS;QACX,CAAC;QACD,6EAA6E;QAC7E,iEAAiE;QACjE,iFAAiF;QACjF,iFAAiF;QACjF,kDAAkD;QAClD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,UAAU,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1D,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEd,MAAM,UAAU,GAAG,uBAAuB,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAC/D,GAAG,CAAC,IAAI,CAAC;YACP,EAAE,EAAE,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC;YAC1E,QAAQ;YACR,GAAG,KAAK;YACR,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,eAAe;YAC9B,SAAS,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI;YACrC,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,OAAO,EAAE,UAAU,CAAC,OAAO;YAC3B,UAAU,EAAE,IAAI,CAAC,eAAe;SACjC,CAAC,CAAC;IACL,CAAC;IACD,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CACR,GAAG,KAAK,KAAK,MAAM,mHAAmH,CACvI,CAAC;IACJ,CAAC;IACD,6EAA6E;IAC7E,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,CACnC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,SAAS,CAAC,CAChF,CAAC;IACF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,MAAM,iEAAiE,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,IAAI,UAAU,GAA0D,IAAI,CAAC;AAC7E,IAAI,aAAa,GAA2C,IAAI,CAAC;AAEjE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAAK,GAAG,KAAK;IACtD,IAAI,YAAY;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAE5E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC,KAAK,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,CAAC,EAAE,GAAG,oBAAoB;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC;IACjG,IAAI,aAAa;QAAE,OAAO,aAAa,CAAC;IAExC,aAAa,GAAG,CAAC,KAAK,IAAqC,EAAE;QAC3D,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,QAAQ,GAAyB,EAAE,CAAC;QACxC,IAAI,CAAC;YACH,4EAA4E;YAC5E,iDAAiD;YACjD,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;YAC3E,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YAC5E,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,+BAA+B,CAAC,CAAC,CAAC;QAC3F,CAAC;QAAC,MAAM,CAAC;YACP,+EAA+E;YAC/E,gFAAgF;YAChF,KAAK,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QACrD,CAAC;QACD,MAAM,MAAM,GAA2B,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC;QAClF,UAAU,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC;QACxC,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QAChB,aAAa,GAAG,IAAI,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,sBAAsB;IACpC,UAAU,GAAG,IAAI,CAAC;AACpB,CAAC;AAcD,SAAS,aAAa,CAAC,KAAmB,EAAE,KAAmB;IAC7D,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,GAAG,GAAuB,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,IAAI,gBAAgB,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,4EAA4E;QAC5E,iEAAiE;QACjE,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,MAAM,SAAS,SAAS,EAAE,CAAC;QAC5C,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC;YACP,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,IAAI;YAC5B,aAAa,EAAE,SAAS;YACxB,IAAI,EAAE;gBACJ,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,QAAQ;gBAChC,OAAO,EAAE,IAAI,EAAE,OAAO;gBACtB,UAAU,EAAE,IAAI,EAAE,UAAU;aAC7B;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAwB;IAC7D,OAAO,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,IAAI,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAClH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAAmB;IAC7D,IAAI,YAAY;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAE5E,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAkB,CAAC,CAAC;IAClF,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAE1C,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QACtB,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,aAAa,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,QAAQ,CAAC,IAAI,CACX,GAAG,cAAc,CACf,GAAG,CAAC,MAAM,EACV,QAAQ,EACR,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,aAAa,EAAE,EAChC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,aAAa,EAAE,EACxE,KAAK,EACL,KAAK,CACN,CACF,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,6CAA6C,CAAC,CAAC,CAAC;QACvG,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,+CAA+C,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC;AACpD,CAAC;AAED,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAgB;IACxD,OAAO;QACL,MAAM;QACN,GAAG,sBAAsB,CAAC,IAAI,CAAC;QAC/B,eAAe,CAAC,IAAI,CAAC;QACrB,WAAW,CAAC,yBAAyB,CAAC,uBAAuB,EAAE,CAAC,CAAC;KAClE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAAmB;IAC7D,IAAI,YAAY;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAE5E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAE1C,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,qBAAqB,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;gBAC5G,SAAS,EAAE,wBAAwB;aACpC,CAAC,CAAC;YACH,QAAQ,CAAC,IAAI,CACX,GAAG,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAC3G,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC;QACnF,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC;AACpD,CAAC;AAiBD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,GAA4B;IACxE,MAAM,KAAK,GAA6B,EAAE,CAAC;IAC3C,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3E,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,qBAAqB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IACtF,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,qBAAqB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IAEtF,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC1C,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;QACjF,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;KACrC,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,OAAO,eAAe,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAkB,CAAC,CAAC;AACvE,CAAC;AAED,sCAAsC;AACtC,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,OAAO,eAAe,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAkB,CAAC,CAAC;AACvE,CAAC"}
|