@wastedcode/claudemux 0.2.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/CHANGELOG.md +257 -0
- package/LICENSE +21 -0
- package/README.md +493 -0
- package/bin/claudemux +6 -0
- package/dist/agents/claude.d.ts +3 -0
- package/dist/agents/claude.d.ts.map +1 -0
- package/dist/agents/claude.js +585 -0
- package/dist/agents/claude.js.map +1 -0
- package/dist/agents/index.d.ts +2 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +2 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/types.d.ts +252 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/backends/tmux/capture.d.ts +25 -0
- package/dist/backends/tmux/capture.d.ts.map +1 -0
- package/dist/backends/tmux/capture.js +35 -0
- package/dist/backends/tmux/capture.js.map +1 -0
- package/dist/backends/tmux/exec.d.ts +105 -0
- package/dist/backends/tmux/exec.d.ts.map +1 -0
- package/dist/backends/tmux/exec.js +226 -0
- package/dist/backends/tmux/exec.js.map +1 -0
- package/dist/backends/tmux/index.d.ts +22 -0
- package/dist/backends/tmux/index.d.ts.map +1 -0
- package/dist/backends/tmux/index.js +108 -0
- package/dist/backends/tmux/index.js.map +1 -0
- package/dist/backends/tmux/keys.d.ts +38 -0
- package/dist/backends/tmux/keys.d.ts.map +1 -0
- package/dist/backends/tmux/keys.js +63 -0
- package/dist/backends/tmux/keys.js.map +1 -0
- package/dist/backends/tmux/options.d.ts +24 -0
- package/dist/backends/tmux/options.d.ts.map +1 -0
- package/dist/backends/tmux/options.js +84 -0
- package/dist/backends/tmux/options.js.map +1 -0
- package/dist/backends/tmux/sessions.d.ts +70 -0
- package/dist/backends/tmux/sessions.d.ts.map +1 -0
- package/dist/backends/tmux/sessions.js +156 -0
- package/dist/backends/tmux/sessions.js.map +1 -0
- package/dist/backends/tmux/socket.d.ts +26 -0
- package/dist/backends/tmux/socket.d.ts.map +1 -0
- package/dist/backends/tmux/socket.js +31 -0
- package/dist/backends/tmux/socket.js.map +1 -0
- package/dist/backends/types.d.ts +110 -0
- package/dist/backends/types.d.ts.map +1 -0
- package/dist/backends/types.js +24 -0
- package/dist/backends/types.js.map +1 -0
- package/dist/cli/ask.d.ts +11 -0
- package/dist/cli/ask.d.ts.map +1 -0
- package/dist/cli/ask.js +17 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/capture.d.ts +8 -0
- package/dist/cli/capture.d.ts.map +1 -0
- package/dist/cli/capture.js +15 -0
- package/dist/cli/capture.js.map +1 -0
- package/dist/cli/context.d.ts +71 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +82 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/exists.d.ts +7 -0
- package/dist/cli/exists.d.ts.map +1 -0
- package/dist/cli/exists.js +16 -0
- package/dist/cli/exists.js.map +1 -0
- package/dist/cli/interrupt.d.ts +10 -0
- package/dist/cli/interrupt.d.ts.map +1 -0
- package/dist/cli/interrupt.js +13 -0
- package/dist/cli/interrupt.js.map +1 -0
- package/dist/cli/kill.d.ts +7 -0
- package/dist/cli/kill.d.ts.map +1 -0
- package/dist/cli/kill.js +14 -0
- package/dist/cli/kill.js.map +1 -0
- package/dist/cli/list.d.ts +10 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +19 -0
- package/dist/cli/list.js.map +1 -0
- package/dist/cli/main.d.ts +13 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +143 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/messages.d.ts +9 -0
- package/dist/cli/messages.d.ts.map +1 -0
- package/dist/cli/messages.js +13 -0
- package/dist/cli/messages.js.map +1 -0
- package/dist/cli/respond.d.ts +10 -0
- package/dist/cli/respond.d.ts.map +1 -0
- package/dist/cli/respond.js +23 -0
- package/dist/cli/respond.js.map +1 -0
- package/dist/cli/resume.d.ts +12 -0
- package/dist/cli/resume.d.ts.map +1 -0
- package/dist/cli/resume.js +21 -0
- package/dist/cli/resume.js.map +1 -0
- package/dist/cli/send.d.ts +9 -0
- package/dist/cli/send.d.ts.map +1 -0
- package/dist/cli/send.js +16 -0
- package/dist/cli/send.js.map +1 -0
- package/dist/cli/spawn.d.ts +14 -0
- package/dist/cli/spawn.d.ts.map +1 -0
- package/dist/cli/spawn.js +21 -0
- package/dist/cli/spawn.js.map +1 -0
- package/dist/cli/state.d.ts +7 -0
- package/dist/cli/state.d.ts.map +1 -0
- package/dist/cli/state.js +11 -0
- package/dist/cli/state.js.map +1 -0
- package/dist/cli/turn-complete.d.ts +8 -0
- package/dist/cli/turn-complete.d.ts.map +1 -0
- package/dist/cli/turn-complete.js +14 -0
- package/dist/cli/turn-complete.js.map +1 -0
- package/dist/cli/wait.d.ts +13 -0
- package/dist/cli/wait.d.ts.map +1 -0
- package/dist/cli/wait.js +17 -0
- package/dist/cli/wait.js.map +1 -0
- package/dist/compose.d.ts +81 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +64 -0
- package/dist/compose.js.map +1 -0
- package/dist/errors.d.ts +250 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +300 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/io/baseline.d.ts +53 -0
- package/dist/io/baseline.d.ts.map +1 -0
- package/dist/io/baseline.js +97 -0
- package/dist/io/baseline.js.map +1 -0
- package/dist/io/capture.d.ts +15 -0
- package/dist/io/capture.d.ts.map +1 -0
- package/dist/io/capture.js +13 -0
- package/dist/io/capture.js.map +1 -0
- package/dist/io/interrupt.d.ts +46 -0
- package/dist/io/interrupt.d.ts.map +1 -0
- package/dist/io/interrupt.js +60 -0
- package/dist/io/interrupt.js.map +1 -0
- package/dist/io/respond.d.ts +28 -0
- package/dist/io/respond.d.ts.map +1 -0
- package/dist/io/respond.js +33 -0
- package/dist/io/respond.js.map +1 -0
- package/dist/io/send.d.ts +44 -0
- package/dist/io/send.d.ts.map +1 -0
- package/dist/io/send.js +66 -0
- package/dist/io/send.js.map +1 -0
- package/dist/io/stabilize.d.ts +28 -0
- package/dist/io/stabilize.d.ts.map +1 -0
- package/dist/io/stabilize.js +20 -0
- package/dist/io/stabilize.js.map +1 -0
- package/dist/io/wait.d.ts +47 -0
- package/dist/io/wait.d.ts.map +1 -0
- package/dist/io/wait.js +117 -0
- package/dist/io/wait.js.map +1 -0
- package/dist/observe/incremental.d.ts +28 -0
- package/dist/observe/incremental.d.ts.map +1 -0
- package/dist/observe/incremental.js +57 -0
- package/dist/observe/incremental.js.map +1 -0
- package/dist/observe/observer.d.ts +86 -0
- package/dist/observe/observer.d.ts.map +1 -0
- package/dist/observe/observer.js +167 -0
- package/dist/observe/observer.js.map +1 -0
- package/dist/observe/session-observer.d.ts +49 -0
- package/dist/observe/session-observer.d.ts.map +1 -0
- package/dist/observe/session-observer.js +123 -0
- package/dist/observe/session-observer.js.map +1 -0
- package/dist/session/adopt.d.ts +52 -0
- package/dist/session/adopt.d.ts.map +1 -0
- package/dist/session/adopt.js +57 -0
- package/dist/session/adopt.js.map +1 -0
- package/dist/session/boot.d.ts +66 -0
- package/dist/session/boot.d.ts.map +1 -0
- package/dist/session/boot.js +216 -0
- package/dist/session/boot.js.map +1 -0
- package/dist/session/constants.d.ts +57 -0
- package/dist/session/constants.d.ts.map +1 -0
- package/dist/session/constants.js +54 -0
- package/dist/session/constants.js.map +1 -0
- package/dist/session/create.d.ts +88 -0
- package/dist/session/create.d.ts.map +1 -0
- package/dist/session/create.js +66 -0
- package/dist/session/create.js.map +1 -0
- package/dist/session/default-backend.d.ts +27 -0
- package/dist/session/default-backend.d.ts.map +1 -0
- package/dist/session/default-backend.js +58 -0
- package/dist/session/default-backend.js.map +1 -0
- package/dist/session/handle.d.ts +63 -0
- package/dist/session/handle.d.ts.map +1 -0
- package/dist/session/handle.js +284 -0
- package/dist/session/handle.js.map +1 -0
- package/dist/session/hooks.d.ts +37 -0
- package/dist/session/hooks.d.ts.map +1 -0
- package/dist/session/hooks.js +42 -0
- package/dist/session/hooks.js.map +1 -0
- package/dist/session/mutex.d.ts +15 -0
- package/dist/session/mutex.d.ts.map +1 -0
- package/dist/session/mutex.js +29 -0
- package/dist/session/mutex.js.map +1 -0
- package/dist/session/recover.d.ts +43 -0
- package/dist/session/recover.d.ts.map +1 -0
- package/dist/session/recover.js +45 -0
- package/dist/session/recover.js.map +1 -0
- package/dist/session/ref.d.ts +2 -0
- package/dist/session/ref.d.ts.map +1 -0
- package/dist/session/ref.js +5 -0
- package/dist/session/ref.js.map +1 -0
- package/dist/session/registry.d.ts +31 -0
- package/dist/session/registry.d.ts.map +1 -0
- package/dist/session/registry.js +32 -0
- package/dist/session/registry.js.map +1 -0
- package/dist/session/resolve.d.ts +30 -0
- package/dist/session/resolve.d.ts.map +1 -0
- package/dist/session/resolve.js +24 -0
- package/dist/session/resolve.js.map +1 -0
- package/dist/session/resume.d.ts +68 -0
- package/dist/session/resume.d.ts.map +1 -0
- package/dist/session/resume.js +54 -0
- package/dist/session/resume.js.map +1 -0
- package/dist/session/spawn-boot.d.ts +44 -0
- package/dist/session/spawn-boot.d.ts.map +1 -0
- package/dist/session/spawn-boot.js +87 -0
- package/dist/session/spawn-boot.js.map +1 -0
- package/dist/session/validate.d.ts +10 -0
- package/dist/session/validate.d.ts.map +1 -0
- package/dist/session/validate.js +0 -0
- package/dist/session/validate.js.map +1 -0
- package/dist/state/classifier.d.ts +29 -0
- package/dist/state/classifier.d.ts.map +1 -0
- package/dist/state/classifier.js +37 -0
- package/dist/state/classifier.js.map +1 -0
- package/dist/state/types.d.ts +32 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +2 -0
- package/dist/state/types.js.map +1 -0
- package/dist/types.d.ts +401 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/util/ansi.d.ts +14 -0
- package/dist/util/ansi.d.ts.map +1 -0
- package/dist/util/ansi.js +18 -0
- package/dist/util/ansi.js.map +1 -0
- package/dist/util/emitter.d.ts +17 -0
- package/dist/util/emitter.d.ts.map +1 -0
- package/dist/util/emitter.js +33 -0
- package/dist/util/emitter.js.map +1 -0
- package/dist/util/sleep.d.ts +8 -0
- package/dist/util/sleep.d.ts.map +1 -0
- package/dist/util/sleep.js +10 -0
- package/dist/util/sleep.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { AgentExitedDuringBoot, BackendUnreachable, DialogStuck, LoginRequired, ReplTimeout, WorkspaceUntrusted, } from "../errors.js";
|
|
2
|
+
import { stabilize } from "../io/stabilize.js";
|
|
3
|
+
import { readHookEdges } from "../observe/observer.js";
|
|
4
|
+
import { sleep } from "../util/sleep.js";
|
|
5
|
+
import { CLASSIFIER_BOTTOM_N, CLASSIFIER_CAPTURE } from "./constants.js";
|
|
6
|
+
import { formatSessionLabel } from "./ref.js";
|
|
7
|
+
/**
|
|
8
|
+
* Default total budget for boot: 60s. The dialog loop must reach a *stable*
|
|
9
|
+
* `agent.boot.isReady` within this window or `ReplTimeout` fires.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_BOOT_TIMEOUT_MS = 60_000;
|
|
12
|
+
/** How long we wait between pane captures while watching for dialog advancement. */
|
|
13
|
+
const POLL_INTERVAL_MS = 150;
|
|
14
|
+
/**
|
|
15
|
+
* After we respond to a dialog, the pane must show *something different*
|
|
16
|
+
* within this window. If the same dialog text persists past it, we throw
|
|
17
|
+
* `DialogStuck` — the response key didn't advance the pane, which means
|
|
18
|
+
* either the matcher fired on stale scrollback or claude's input layer
|
|
19
|
+
* isn't accepting keys (the latter is a setup error worth surfacing loudly).
|
|
20
|
+
*/
|
|
21
|
+
const DIALOG_ADVANCE_BUDGET_MS = 5_000;
|
|
22
|
+
/**
|
|
23
|
+
* Once `isReady` first matches, the pane must hold steady for this window
|
|
24
|
+
* before we declare boot complete. The empty `❯` input box can flash during
|
|
25
|
+
* the welcome / MCP-init render *before input is actually interactive* — a
|
|
26
|
+
* consumer that `send`s into a not-yet-interactive prompt loses the turn
|
|
27
|
+
* silently (the paste lands, never submits). Requiring stability lets the
|
|
28
|
+
* welcome/MCP render settle. Longer than `wait`'s idle window (250ms) because
|
|
29
|
+
* boot has more going on (box draw, remote-control line, MCP connections).
|
|
30
|
+
* See `engineer/wiki/wait-needs-a-transition-not-a-snapshot`.
|
|
31
|
+
*/
|
|
32
|
+
const READY_STABLE_WINDOW_MS = 1_200;
|
|
33
|
+
/**
|
|
34
|
+
* Boot the session: dismiss any matching dialogs in order, then wait for ready.
|
|
35
|
+
*
|
|
36
|
+
* **Ready signal:** a hook *gate* plus a pane *settle*.
|
|
37
|
+
* 1. **`session-start` hook edge — the authoritative "started" gate.** With
|
|
38
|
+
* hooks on (the default, `opts.rendezvousPath` set), boot will not declare
|
|
39
|
+
* ready until this edge fires — a ready-*looking* pane is NOT trusted on
|
|
40
|
+
* its own (the founder's "hooks, not screen-scraping" north star). The edge
|
|
41
|
+
* lands only once input is interactive and post-dialog. With hooks off
|
|
42
|
+
* there is no edge, so the pane is the only signal.
|
|
43
|
+
* 2. **Stable ready box — the delivery-safety settle.** Even after "started,"
|
|
44
|
+
* a fresh REPL is still painting its welcome/MCP render, and the *first*
|
|
45
|
+
* send pasted into that render storm is silently lost (verified). So boot
|
|
46
|
+
* returns only once the ready box has held *stable*, guaranteeing the input
|
|
47
|
+
* is paintable. Dialogs are handled before either check each iteration.
|
|
48
|
+
*
|
|
49
|
+
* Throws on the documented failures.
|
|
50
|
+
*
|
|
51
|
+
* @throws `WorkspaceUntrusted` if the workspace-trust dialog fires and
|
|
52
|
+
* `trustWorkspace` was not set — thrown *before* any keystroke, so no
|
|
53
|
+
* persistent trust flag is written.
|
|
54
|
+
* @throws `LoginRequired` if the login-method dialog fires.
|
|
55
|
+
* @throws `DialogStuck` if a recognized dialog persists after its response.
|
|
56
|
+
* @throws `AgentExitedDuringBoot` if the agent process exits (its session is
|
|
57
|
+
* reaped) before becoming ready — most often an `agentSessionId` collision.
|
|
58
|
+
* @throws `ReplTimeout` if the total budget elapses before a stable ready.
|
|
59
|
+
*/
|
|
60
|
+
export async function bootSession(backend, agent, ref, opts = {}) {
|
|
61
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_BOOT_TIMEOUT_MS;
|
|
62
|
+
const trustWorkspace = opts.trustWorkspace ?? false;
|
|
63
|
+
const cwd = opts.cwd ?? ref.name;
|
|
64
|
+
const start = Date.now();
|
|
65
|
+
// The rendezvous is reused across resume, so it may ALREADY hold the prior
|
|
66
|
+
// life's `session-start`. Baseline the count now and wait for a NEW one — else
|
|
67
|
+
// a resume boots "ready" on a stale edge (clock-independent: count, not time).
|
|
68
|
+
const priorStarts = countSessionStarts(agent, opts.rendezvousPath);
|
|
69
|
+
while (true) {
|
|
70
|
+
if (Date.now() - start > timeoutMs) {
|
|
71
|
+
throw new ReplTimeout(formatSessionLabel(ref), timeoutMs);
|
|
72
|
+
}
|
|
73
|
+
const text = await captureDuringBoot(backend, ref, opts.agentSessionId);
|
|
74
|
+
// Try every dialog matcher in order — the first that fires wins. Dialogs
|
|
75
|
+
// are always handled before any ready check (hook or pane), so neither can
|
|
76
|
+
// declare ready while a dialog is still on screen.
|
|
77
|
+
const matched = agent.boot.dialogs.find((d) => d.matches(text));
|
|
78
|
+
if (matched) {
|
|
79
|
+
await respondToDialog(backend, ref, matched, trustWorkspace, cwd);
|
|
80
|
+
// Wait for the pane to advance past the matched dialog. If it stays,
|
|
81
|
+
// either the response didn't land or the matcher misfired on history.
|
|
82
|
+
await waitForAdvancement(backend, ref, matched);
|
|
83
|
+
continue; // re-evaluate from the top
|
|
84
|
+
}
|
|
85
|
+
// Authoritative "session has started" gate: the agent's SessionStart hook
|
|
86
|
+
// edge (when hooks are on). The edge only lands once input is interactive
|
|
87
|
+
// and post-dialog, so we DON'T trust a ready-looking pane until it fires —
|
|
88
|
+
// that is the founder's "hooks, not screen-scraping" north star. With hooks
|
|
89
|
+
// off, there is no edge, so the pane is the only signal.
|
|
90
|
+
const hooksOn = opts.rendezvousPath !== undefined;
|
|
91
|
+
const started = !hooksOn || countSessionStarts(agent, opts.rendezvousPath) > priorStarts;
|
|
92
|
+
if (!started) {
|
|
93
|
+
await sleep(POLL_INTERVAL_MS); // session not started yet — wait for the edge
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Started — but a fresh REPL is still painting its welcome/MCP render, and a
|
|
97
|
+
// paste into that render storm is silently lost (verified: the first send
|
|
98
|
+
// races a repaint). So gate the RETURN on a *stable* ready box: the hook
|
|
99
|
+
// says "started", the pane settle says "the input is paintable now." The
|
|
100
|
+
// empty box can also flash mid-render, so requiring it to hold is necessary
|
|
101
|
+
// regardless of the hook.
|
|
102
|
+
if (agent.boot.isReady(text)) {
|
|
103
|
+
const remaining = Math.max(0, timeoutMs - (Date.now() - start));
|
|
104
|
+
const r = await stabilize(backend, ref, {
|
|
105
|
+
lines: CLASSIFIER_BOTTOM_N,
|
|
106
|
+
windowMs: READY_STABLE_WINDOW_MS,
|
|
107
|
+
pollMs: POLL_INTERVAL_MS,
|
|
108
|
+
timeoutMs: Math.min(remaining, READY_STABLE_WINDOW_MS * 6),
|
|
109
|
+
ansi: true,
|
|
110
|
+
});
|
|
111
|
+
// Re-confirm ready on the settled pane (the render may have moved to a
|
|
112
|
+
// dialog or back to working; only a stable ready counts).
|
|
113
|
+
if (r.stable && agent.boot.isReady(r.text) && !anyDialog(agent, r.text)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
continue; // not stable yet (still rendering) — loop and re-evaluate
|
|
117
|
+
}
|
|
118
|
+
// Started but the box isn't ready yet (still rendering) — keep polling.
|
|
119
|
+
await sleep(POLL_INTERVAL_MS);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function anyDialog(agent, text) {
|
|
123
|
+
return agent.boot.dialogs.some((d) => d.matches(text));
|
|
124
|
+
}
|
|
125
|
+
/** How many `session-start` edges the rendezvous holds (0 when hooks off/absent). */
|
|
126
|
+
function countSessionStarts(agent, rendezvousPath) {
|
|
127
|
+
if (rendezvousPath === undefined)
|
|
128
|
+
return 0;
|
|
129
|
+
return readHookEdges({ agent, rendezvousPath }).filter((e) => e.event === "session-start").length;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Capture the boot pane, distinguishing **the agent exiting before ready**
|
|
133
|
+
* from a **backend-level fault**. The agent runs with `remain-on-exit off`, so
|
|
134
|
+
* a fast exit (the dominant case: an `agentSessionId` collision — see
|
|
135
|
+
* {@link AgentExitedDuringBoot}) reaps the session; the next capture then fails
|
|
136
|
+
* because the session is gone, not because anything is wrong with us.
|
|
137
|
+
*
|
|
138
|
+
* On a capture failure we branch:
|
|
139
|
+
* - {@link BackendUnreachable} → a server-level fault (no server / wedged /
|
|
140
|
+
* spawn-failed) — surface it unchanged; it is not "the agent exited."
|
|
141
|
+
* - otherwise, probe liveness: if the session is genuinely gone, the agent
|
|
142
|
+
* exited before ready → {@link AgentExitedDuringBoot} (fast, no waiting out
|
|
143
|
+
* the 60s `ReplTimeout`). If it is still alive, the capture hiccuped for
|
|
144
|
+
* some other reason — surface the original error honestly.
|
|
145
|
+
*
|
|
146
|
+
* This must not mask the *alive-pane* boot failures (`LoginRequired`,
|
|
147
|
+
* `WorkspaceUntrusted`, `DialogStuck`, `ReplTimeout`): those fire on captured
|
|
148
|
+
* pane text or the timeout, never on a capture failure, so they are untouched.
|
|
149
|
+
*/
|
|
150
|
+
async function captureDuringBoot(backend, ref, agentSessionId) {
|
|
151
|
+
try {
|
|
152
|
+
return await backend.capture(ref, CLASSIFIER_CAPTURE);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
if (err instanceof BackendUnreachable)
|
|
156
|
+
throw err;
|
|
157
|
+
const alive = await backend.exists(ref).catch(() => false);
|
|
158
|
+
if (!alive) {
|
|
159
|
+
throw new AgentExitedDuringBoot(formatSessionLabel(ref), agentSessionId);
|
|
160
|
+
}
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Respond to a matched dialog.
|
|
166
|
+
*
|
|
167
|
+
* - `respond.kind === "throw"` → raise the typed error.
|
|
168
|
+
* - A **gated** dialog (an authority grant, e.g. workspace-trust) → throw the
|
|
169
|
+
* gate's error *before sending any key* unless the consumer opted in. The
|
|
170
|
+
* throw-before-keystroke order is load-bearing: answering the trust dialog
|
|
171
|
+
* writes a persistent trust flag, so we must not send the key on the
|
|
172
|
+
* fail-closed path.
|
|
173
|
+
* - `respond.kind === "key"` → send the key; non-Enter keys get an Enter
|
|
174
|
+
* follow-up to submit (Enter is its own submit).
|
|
175
|
+
*/
|
|
176
|
+
async function respondToDialog(backend, ref, dialog, trustWorkspace, cwd) {
|
|
177
|
+
if (dialog.respond.kind === "throw") {
|
|
178
|
+
switch (dialog.respond.errorClass) {
|
|
179
|
+
case "LoginRequired":
|
|
180
|
+
throw new LoginRequired(formatSessionLabel(ref));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Authority gate: fail closed unless opted in — BEFORE any keystroke.
|
|
184
|
+
if (dialog.gate) {
|
|
185
|
+
const optedIn = dialog.gate.option === "trustWorkspace" && trustWorkspace;
|
|
186
|
+
if (!optedIn) {
|
|
187
|
+
switch (dialog.gate.errorClass) {
|
|
188
|
+
case "WorkspaceUntrusted":
|
|
189
|
+
throw new WorkspaceUntrusted(formatSessionLabel(ref), cwd);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const key = dialog.respond.key;
|
|
194
|
+
await backend.send(ref, { kind: "key", key });
|
|
195
|
+
// Numeric/letter dialog responses (1, 2, y, n) typically need an Enter to
|
|
196
|
+
// submit; Enter is its own submit, so no follow-up needed there.
|
|
197
|
+
if (key !== "Enter") {
|
|
198
|
+
await backend.send(ref, { kind: "key", key: "Enter" });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Wait until the pane no longer matches the just-responded dialog. If the
|
|
203
|
+
* matcher keeps firing past {@link DIALOG_ADVANCE_BUDGET_MS}, the response
|
|
204
|
+
* didn't land — throw `DialogStuck` with the dialog's id.
|
|
205
|
+
*/
|
|
206
|
+
async function waitForAdvancement(backend, ref, matched) {
|
|
207
|
+
const start = Date.now();
|
|
208
|
+
while (Date.now() - start < DIALOG_ADVANCE_BUDGET_MS) {
|
|
209
|
+
await sleep(POLL_INTERVAL_MS);
|
|
210
|
+
const now = await backend.capture(ref, CLASSIFIER_CAPTURE);
|
|
211
|
+
if (!matched.matches(now))
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
throw new DialogStuck(formatSessionLabel(ref), matched.id);
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=boot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.js","sourceRoot":"","sources":["../../src/session/boot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The default namespace prefix used by `create`, `exists`, `kill`, `list`
|
|
3
|
+
* when the caller doesn't pass one. Two consumers on the same machine
|
|
4
|
+
* pick distinct namespaces to coexist on one shared backend server.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_NAMESPACE = "claudemux";
|
|
7
|
+
/**
|
|
8
|
+
* Pane height the substrate creates sessions at (`new-session -y`). The
|
|
9
|
+
* classifier never sees more than this many rows, because `capture-pane -p`
|
|
10
|
+
* returns only the visible region (never scrollback) — so a stray match in
|
|
11
|
+
* history is impossible by construction, regardless of the scan window.
|
|
12
|
+
*
|
|
13
|
+
* If this changes, {@link CLASSIFIER_BOTTOM_N} should track it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PANE_HEIGHT = 40;
|
|
16
|
+
/**
|
|
17
|
+
* How many lines from the bottom of the visible region the classifier scans.
|
|
18
|
+
* Set to the full pane height ({@link PANE_HEIGHT}): the visible region is
|
|
19
|
+
* already only {@link PANE_HEIGHT} rows, so `slice(-CLASSIFIER_BOTTOM_N)` is
|
|
20
|
+
* the whole capture — there is no larger buffer to trim. (An earlier value of
|
|
21
|
+
* 50 implied a 50-line scan that a 40-row pane can never produce; this ties
|
|
22
|
+
* the constant to the real cap.)
|
|
23
|
+
*
|
|
24
|
+
* Shared by `session/boot.ts`, `session/handle.ts`, and `io/wait.ts` so a
|
|
25
|
+
* dialog/idle pattern is detected consistently across the boot and wait paths.
|
|
26
|
+
*/
|
|
27
|
+
export declare const CLASSIFIER_BOTTOM_N = 40;
|
|
28
|
+
/**
|
|
29
|
+
* Capture options for any **readiness / classifier** read — bottom-N rows with
|
|
30
|
+
* ANSI styling **on** (`capture -e`). The styling is load-bearing: the agent's
|
|
31
|
+
* idle check separates the dim ghost-placeholder hint shown in an empty input
|
|
32
|
+
* box from a real (normal-intensity) draft, which is impossible on plain text
|
|
33
|
+
* (see claude `isReady`). Substring predicates (dialog headers, working) strip
|
|
34
|
+
* SGR first, so ANSI-on is safe for them too.
|
|
35
|
+
*
|
|
36
|
+
* Every readiness read uses this one shape so the `send→wait` pane fingerprints
|
|
37
|
+
* stay self-consistent (all ANSI). The *public* `capture()` is unaffected — it
|
|
38
|
+
* defaults to plain, user-facing text.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CLASSIFIER_CAPTURE: {
|
|
41
|
+
readonly lines: 40;
|
|
42
|
+
readonly ansi: true;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Backend-neutral session-meta key under which {@link create} caches the
|
|
46
|
+
* agent's conversation id, and {@link adopt} reads it back. The id is a
|
|
47
|
+
* *locator* (it names a conversation and its transcript), not a secret; the
|
|
48
|
+
* real boundary is who can reach the backend's per-session store — today a
|
|
49
|
+
* per-process private tmux socket. If claudemux ever multi-tenants onto a
|
|
50
|
+
* *shared* tmux server, a co-tenant could enumerate every such key and locate
|
|
51
|
+
* others' transcripts; revisit the store's trust model then.
|
|
52
|
+
*
|
|
53
|
+
* One authoritative spelling shared by the writer and the reader so they can
|
|
54
|
+
* never drift apart.
|
|
55
|
+
*/
|
|
56
|
+
export declare const AGENT_SESSION_ID_META_KEY = "agent-session-id";
|
|
57
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/session/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,KAAc,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;;;CAAsD,CAAC;AAEtF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The default namespace prefix used by `create`, `exists`, `kill`, `list`
|
|
3
|
+
* when the caller doesn't pass one. Two consumers on the same machine
|
|
4
|
+
* pick distinct namespaces to coexist on one shared backend server.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_NAMESPACE = "claudemux";
|
|
7
|
+
/**
|
|
8
|
+
* Pane height the substrate creates sessions at (`new-session -y`). The
|
|
9
|
+
* classifier never sees more than this many rows, because `capture-pane -p`
|
|
10
|
+
* returns only the visible region (never scrollback) — so a stray match in
|
|
11
|
+
* history is impossible by construction, regardless of the scan window.
|
|
12
|
+
*
|
|
13
|
+
* If this changes, {@link CLASSIFIER_BOTTOM_N} should track it.
|
|
14
|
+
*/
|
|
15
|
+
export const PANE_HEIGHT = 40;
|
|
16
|
+
/**
|
|
17
|
+
* How many lines from the bottom of the visible region the classifier scans.
|
|
18
|
+
* Set to the full pane height ({@link PANE_HEIGHT}): the visible region is
|
|
19
|
+
* already only {@link PANE_HEIGHT} rows, so `slice(-CLASSIFIER_BOTTOM_N)` is
|
|
20
|
+
* the whole capture — there is no larger buffer to trim. (An earlier value of
|
|
21
|
+
* 50 implied a 50-line scan that a 40-row pane can never produce; this ties
|
|
22
|
+
* the constant to the real cap.)
|
|
23
|
+
*
|
|
24
|
+
* Shared by `session/boot.ts`, `session/handle.ts`, and `io/wait.ts` so a
|
|
25
|
+
* dialog/idle pattern is detected consistently across the boot and wait paths.
|
|
26
|
+
*/
|
|
27
|
+
export const CLASSIFIER_BOTTOM_N = PANE_HEIGHT;
|
|
28
|
+
/**
|
|
29
|
+
* Capture options for any **readiness / classifier** read — bottom-N rows with
|
|
30
|
+
* ANSI styling **on** (`capture -e`). The styling is load-bearing: the agent's
|
|
31
|
+
* idle check separates the dim ghost-placeholder hint shown in an empty input
|
|
32
|
+
* box from a real (normal-intensity) draft, which is impossible on plain text
|
|
33
|
+
* (see claude `isReady`). Substring predicates (dialog headers, working) strip
|
|
34
|
+
* SGR first, so ANSI-on is safe for them too.
|
|
35
|
+
*
|
|
36
|
+
* Every readiness read uses this one shape so the `send→wait` pane fingerprints
|
|
37
|
+
* stay self-consistent (all ANSI). The *public* `capture()` is unaffected — it
|
|
38
|
+
* defaults to plain, user-facing text.
|
|
39
|
+
*/
|
|
40
|
+
export const CLASSIFIER_CAPTURE = { lines: CLASSIFIER_BOTTOM_N, ansi: true };
|
|
41
|
+
/**
|
|
42
|
+
* Backend-neutral session-meta key under which {@link create} caches the
|
|
43
|
+
* agent's conversation id, and {@link adopt} reads it back. The id is a
|
|
44
|
+
* *locator* (it names a conversation and its transcript), not a secret; the
|
|
45
|
+
* real boundary is who can reach the backend's per-session store — today a
|
|
46
|
+
* per-process private tmux socket. If claudemux ever multi-tenants onto a
|
|
47
|
+
* *shared* tmux server, a co-tenant could enumerate every such key and locate
|
|
48
|
+
* others' transcripts; revisit the store's trust model then.
|
|
49
|
+
*
|
|
50
|
+
* One authoritative spelling shared by the writer and the reader so they can
|
|
51
|
+
* never drift apart.
|
|
52
|
+
*/
|
|
53
|
+
export const AGENT_SESSION_ID_META_KEY = "agent-session-id";
|
|
54
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/session/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,IAAI,EAAW,CAAC;AAEtF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AgentDef } from "../agents/types.js";
|
|
2
|
+
import type { Backend } from "../backends/types.js";
|
|
3
|
+
import type { SessionHandle } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link create}. The substrate provides sensible defaults so
|
|
6
|
+
* the canonical call is `create({ name, cwd })`.
|
|
7
|
+
*/
|
|
8
|
+
export interface CreateOptions {
|
|
9
|
+
/** Session name within the namespace. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Working directory the agent runs in. */
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** Namespace prefix (default: `"claudemux"`). Lets two consumers coexist. */
|
|
14
|
+
namespace?: string;
|
|
15
|
+
/** Agent definition (default: claude). */
|
|
16
|
+
agent?: AgentDef;
|
|
17
|
+
/** Backend instance (default: tmux on a fresh shared socket per process). */
|
|
18
|
+
backend?: Backend;
|
|
19
|
+
/** Extra args passed to the agent's argv. */
|
|
20
|
+
extraArgs?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Choose the conversation id for this **fresh** session, instead of letting
|
|
23
|
+
* the substrate mint one. Must be a v4 UUID (validated before spawn —
|
|
24
|
+
* {@link InvalidAgentSessionId} otherwise). The chosen id is what
|
|
25
|
+
* {@link SessionHandle.agentSessionId} reports. Leave unset for the common
|
|
26
|
+
* case: the substrate mints a v4 UUID for you.
|
|
27
|
+
*
|
|
28
|
+
* Resume is **not** this option — use the first-class {@link resume} (or, for
|
|
29
|
+
* an advanced case, an `extraArgs` `--resume <id>`). Passing both this and an
|
|
30
|
+
* `extraArgs` identity flag (`--session-id` / `--resume` / `--fork-session`)
|
|
31
|
+
* is a conflict → {@link AgentSessionIdConflict}.
|
|
32
|
+
*/
|
|
33
|
+
agentSessionId?: string;
|
|
34
|
+
/** Override env passed to the agent process. */
|
|
35
|
+
env?: Record<string, string>;
|
|
36
|
+
/** Boot timeout in ms (default 60_000). */
|
|
37
|
+
bootTimeoutMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Opt in to auto-dismissing the agent's workspace-trust dialog. Default
|
|
40
|
+
* **false** (fail closed). When the agent asks to trust `cwd` and this is
|
|
41
|
+
* not set, `create` throws `WorkspaceUntrusted` before any keystroke —
|
|
42
|
+
* trusting a folder is an authority grant the substrate won't make
|
|
43
|
+
* silently. See {@link WorkspaceUntrusted} for the persistent/global-trust
|
|
44
|
+
* caveats before enabling it for untrusted-fork (PR-bot / CI) workloads.
|
|
45
|
+
*/
|
|
46
|
+
trustWorkspace?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Inject the agent's observe hooks at spawn (default **true**). Hooks give
|
|
49
|
+
* claudemux deterministic, reliable turn-lifecycle insight without scraping
|
|
50
|
+
* the TUI. Set `false` to opt out (e.g. you manage your own hooks); observe
|
|
51
|
+
* then degrades to the best-effort pane+transcript fallback. The exact
|
|
52
|
+
* injected settings are inspectable via the agent's hook spec.
|
|
53
|
+
*/
|
|
54
|
+
hooks?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create a new session: spawn the agent, dismiss boot dialogs, wait for
|
|
58
|
+
* ready, return a handle.
|
|
59
|
+
*
|
|
60
|
+
* @throws `SessionExists` if a session with the same `{ namespace, name }`
|
|
61
|
+
* already exists — the substrate never silently adopts.
|
|
62
|
+
* @throws `InvalidAgentSessionId` if `agentSessionId` is supplied but is not a
|
|
63
|
+
* v4 UUID (thrown before spawn).
|
|
64
|
+
* @throws `AgentSessionIdConflict` if `agentSessionId` is supplied alongside an
|
|
65
|
+
* `extraArgs` identity flag (`--session-id` / `--resume` / `--fork-session`)
|
|
66
|
+
* — the id was chosen two ways (thrown before spawn).
|
|
67
|
+
* @throws `LoginRequired` if claude's login-method dialog fires (the
|
|
68
|
+
* consumer must `claude auth` first).
|
|
69
|
+
* @throws `WorkspaceUntrusted` if the agent asks to trust `cwd` and
|
|
70
|
+
* `trustWorkspace` was not set (thrown before any keystroke).
|
|
71
|
+
* @throws `DialogStuck` if a recognized boot dialog persists after its
|
|
72
|
+
* response.
|
|
73
|
+
* @throws `AgentExitedDuringBoot` if the agent exits before becoming ready —
|
|
74
|
+
* most often an `agentSessionId` collision (the agent refuses to silently
|
|
75
|
+
* resume an in-use id and exits). The id is carried on the error.
|
|
76
|
+
* @throws `ReplTimeout` if the boot budget elapses before ready.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { create, claude } from "claudemux";
|
|
81
|
+
* const session = await create({ name: "job", cwd: process.cwd() });
|
|
82
|
+
* await session.send("Add a CHANGELOG entry");
|
|
83
|
+
* await session.wait();
|
|
84
|
+
* const text = await session.capture();
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare function create(opts: CreateOptions): Promise<SessionHandle>;
|
|
88
|
+
//# sourceMappingURL=create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/session/create.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA+BxE"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SessionExists } from "../errors.js";
|
|
2
|
+
import { formatSessionLabel } from "./ref.js";
|
|
3
|
+
import { resolveSessionContext } from "./resolve.js";
|
|
4
|
+
import { spawnBootHandle } from "./spawn-boot.js";
|
|
5
|
+
import { validateAgentSessionId } from "./validate.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create a new session: spawn the agent, dismiss boot dialogs, wait for
|
|
8
|
+
* ready, return a handle.
|
|
9
|
+
*
|
|
10
|
+
* @throws `SessionExists` if a session with the same `{ namespace, name }`
|
|
11
|
+
* already exists — the substrate never silently adopts.
|
|
12
|
+
* @throws `InvalidAgentSessionId` if `agentSessionId` is supplied but is not a
|
|
13
|
+
* v4 UUID (thrown before spawn).
|
|
14
|
+
* @throws `AgentSessionIdConflict` if `agentSessionId` is supplied alongside an
|
|
15
|
+
* `extraArgs` identity flag (`--session-id` / `--resume` / `--fork-session`)
|
|
16
|
+
* — the id was chosen two ways (thrown before spawn).
|
|
17
|
+
* @throws `LoginRequired` if claude's login-method dialog fires (the
|
|
18
|
+
* consumer must `claude auth` first).
|
|
19
|
+
* @throws `WorkspaceUntrusted` if the agent asks to trust `cwd` and
|
|
20
|
+
* `trustWorkspace` was not set (thrown before any keystroke).
|
|
21
|
+
* @throws `DialogStuck` if a recognized boot dialog persists after its
|
|
22
|
+
* response.
|
|
23
|
+
* @throws `AgentExitedDuringBoot` if the agent exits before becoming ready —
|
|
24
|
+
* most often an `agentSessionId` collision (the agent refuses to silently
|
|
25
|
+
* resume an in-use id and exits). The id is carried on the error.
|
|
26
|
+
* @throws `ReplTimeout` if the boot budget elapses before ready.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { create, claude } from "claudemux";
|
|
31
|
+
* const session = await create({ name: "job", cwd: process.cwd() });
|
|
32
|
+
* await session.send("Add a CHANGELOG entry");
|
|
33
|
+
* await session.wait();
|
|
34
|
+
* const text = await session.capture();
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export async function create(opts) {
|
|
38
|
+
const { ref, agent, backend } = resolveSessionContext(opts);
|
|
39
|
+
// Exists-check first. Never silently adopt an existing session — that is
|
|
40
|
+
// the lifecycle-policy footgun claudemux explicitly avoids.
|
|
41
|
+
if (await backend.exists(ref)) {
|
|
42
|
+
throw new SessionExists(formatSessionLabel(ref));
|
|
43
|
+
}
|
|
44
|
+
// Resolve the FRESH conversation id. The mint is neutral (crypto.randomUUID —
|
|
45
|
+
// no agent vocabulary); a caller-supplied id is validated as a v4 UUID. The
|
|
46
|
+
// agent's buildArgv decides how it becomes a flag and returns the id that will
|
|
47
|
+
// actually run, which `spawnBootHandle` surfaces (single source of truth).
|
|
48
|
+
const explicit = opts.agentSessionId !== undefined;
|
|
49
|
+
if (explicit) {
|
|
50
|
+
validateAgentSessionId(opts.agentSessionId);
|
|
51
|
+
}
|
|
52
|
+
const sessionId = explicit ? opts.agentSessionId : crypto.randomUUID();
|
|
53
|
+
return spawnBootHandle({
|
|
54
|
+
agent,
|
|
55
|
+
backend,
|
|
56
|
+
ref,
|
|
57
|
+
cwd: opts.cwd,
|
|
58
|
+
identity: { mode: "fresh", sessionId, explicit },
|
|
59
|
+
hooks: opts.hooks !== false,
|
|
60
|
+
...(opts.extraArgs === undefined ? {} : { extraArgs: opts.extraArgs }),
|
|
61
|
+
...(opts.env === undefined ? {} : { env: opts.env }),
|
|
62
|
+
...(opts.bootTimeoutMs === undefined ? {} : { bootTimeoutMs: opts.bootTimeoutMs }),
|
|
63
|
+
...(opts.trustWorkspace === undefined ? {} : { trustWorkspace: opts.trustWorkspace }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/session/create.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAuDvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAmB;IAC9C,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAE5D,yEAAyE;IACzE,4DAA4D;IAC5D,IAAI,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,8EAA8E;IAC9E,4EAA4E;IAC5E,+EAA+E;IAC/E,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC;IACnD,IAAI,QAAQ,EAAE,CAAC;QACb,sBAAsB,CAAC,IAAI,CAAC,cAAwB,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,cAAyB,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAEnF,OAAO,eAAe,CAAC;QACrB,KAAK;QACL,OAAO;QACP,GAAG;QACH,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;QAChD,KAAK,EAAE,IAAI,CAAC,KAAK,KAAK,KAAK;QAC3B,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QACtE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;QACpD,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC;QAClF,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;KACtF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Backend } from "../backends/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Single-source socket-name resolution. Precedence (highest first):
|
|
4
|
+
*
|
|
5
|
+
* `explicit` (`--socket` flag) > `CLAUDEMUX_SOCKET` env > `defaultSocketName()`
|
|
6
|
+
*
|
|
7
|
+
* Every candidate is **trimmed**, and the trimmed value is what's both gated
|
|
8
|
+
* AND returned — so `" claudemux "` resolves to the same socket as the bare
|
|
9
|
+
* default, instead of a silently-divergent `-L ' claudemux '` server that
|
|
10
|
+
* re-opens the cross-process rendezvous bug the P0 fix closed (QA P2,
|
|
11
|
+
* [[decisions/0006-default-backend-rendezvous-identity]]). A whitespace-only
|
|
12
|
+
* value is treated as "not set" and falls through to the next candidate.
|
|
13
|
+
*
|
|
14
|
+
* Lives in this bootstrap module so the backend leaf
|
|
15
|
+
* (`backends/tmux/socket.ts`) stays a pure function — env/flag composition
|
|
16
|
+
* is a bootstrap concern, not a backend concern.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveSocket(explicit?: string): string;
|
|
19
|
+
export declare function sharedDefaultBackend(): Backend;
|
|
20
|
+
/**
|
|
21
|
+
* Build a backend with an explicit socket name. Used by the CLI when the
|
|
22
|
+
* caller passes `--socket <name>` to opt out of the process-wide shared
|
|
23
|
+
* default. Each call returns a fresh instance (no caching) — the caller
|
|
24
|
+
* owns the lifetime.
|
|
25
|
+
*/
|
|
26
|
+
export declare function backendWithSocket(socket: string): Backend;
|
|
27
|
+
//# sourceMappingURL=default-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-backend.d.ts","sourceRoot":"","sources":["../../src/session/default-backend.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvD;AAkBD,wBAAgB,oBAAoB,IAAI,OAAO,CAK9C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { tmuxBackend } from "../backends/tmux/index.js";
|
|
2
|
+
import { defaultSocketName } from "../backends/tmux/socket.js";
|
|
3
|
+
/**
|
|
4
|
+
* Single-source socket-name resolution. Precedence (highest first):
|
|
5
|
+
*
|
|
6
|
+
* `explicit` (`--socket` flag) > `CLAUDEMUX_SOCKET` env > `defaultSocketName()`
|
|
7
|
+
*
|
|
8
|
+
* Every candidate is **trimmed**, and the trimmed value is what's both gated
|
|
9
|
+
* AND returned — so `" claudemux "` resolves to the same socket as the bare
|
|
10
|
+
* default, instead of a silently-divergent `-L ' claudemux '` server that
|
|
11
|
+
* re-opens the cross-process rendezvous bug the P0 fix closed (QA P2,
|
|
12
|
+
* [[decisions/0006-default-backend-rendezvous-identity]]). A whitespace-only
|
|
13
|
+
* value is treated as "not set" and falls through to the next candidate.
|
|
14
|
+
*
|
|
15
|
+
* Lives in this bootstrap module so the backend leaf
|
|
16
|
+
* (`backends/tmux/socket.ts`) stays a pure function — env/flag composition
|
|
17
|
+
* is a bootstrap concern, not a backend concern.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveSocket(explicit) {
|
|
20
|
+
const fromFlag = explicit?.trim();
|
|
21
|
+
if (fromFlag)
|
|
22
|
+
return fromFlag;
|
|
23
|
+
const fromEnv = process.env.CLAUDEMUX_SOCKET?.trim();
|
|
24
|
+
if (fromEnv)
|
|
25
|
+
return fromEnv;
|
|
26
|
+
return defaultSocketName();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Process-wide default backend. Lazily constructed on first use so an
|
|
30
|
+
* `import { exists } from "claudemux"` doesn't spawn a tmux server just by
|
|
31
|
+
* loading the module.
|
|
32
|
+
*
|
|
33
|
+
* The socket name is **stable** (default `"claudemux"`, overridable via
|
|
34
|
+
* `CLAUDEMUX_SOCKET`). Stability is load-bearing for the CLI: each
|
|
35
|
+
* `claudemux <verb>` invocation is a cold Node process, and they must
|
|
36
|
+
* discover each other's sessions. A per-process random socket would break
|
|
37
|
+
* the entire CLI user journey — see `brain/initiatives/.../qa.md` §P0-1
|
|
38
|
+
* and `brain/decisions/0006-default-backend-rendezvous-identity.md`.
|
|
39
|
+
*
|
|
40
|
+
* Internal — never exported from `src/index.ts`.
|
|
41
|
+
*/
|
|
42
|
+
let cached = null;
|
|
43
|
+
export function sharedDefaultBackend() {
|
|
44
|
+
if (cached === null) {
|
|
45
|
+
cached = tmuxBackend({ socket: resolveSocket() });
|
|
46
|
+
}
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a backend with an explicit socket name. Used by the CLI when the
|
|
51
|
+
* caller passes `--socket <name>` to opt out of the process-wide shared
|
|
52
|
+
* default. Each call returns a fresh instance (no caching) — the caller
|
|
53
|
+
* owns the lifetime.
|
|
54
|
+
*/
|
|
55
|
+
export function backendWithSocket(socket) {
|
|
56
|
+
return tmuxBackend({ socket });
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=default-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-backend.js","sourceRoot":"","sources":["../../src/session/default-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,aAAa,CAAC,QAAiB;IAC7C,MAAM,QAAQ,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IAClC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,OAAO,iBAAiB,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,IAAI,MAAM,GAAmB,IAAI,CAAC;AAElC,MAAM,UAAU,oBAAoB;IAClC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,GAAG,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AACjC,CAAC"}
|