@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
package/dist/errors.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors thrown by the claudemux substrate. Every error carries the
|
|
3
|
+
* session name in its message so a consumer logging an unknown failure
|
|
4
|
+
* still has the context they need.
|
|
5
|
+
*
|
|
6
|
+
* No bare `Error` is ever thrown from the library. The classes here are
|
|
7
|
+
* exhaustive for the current public surface.
|
|
8
|
+
*/
|
|
9
|
+
/** Base class — consumers can `catch (e: ClaudemuxError)` for the union. */
|
|
10
|
+
export class ClaudemuxError extends Error {
|
|
11
|
+
/** The session this error pertains to. */
|
|
12
|
+
sessionName;
|
|
13
|
+
constructor(message, sessionName) {
|
|
14
|
+
super(`[claudemux:${sessionName}] ${message}`);
|
|
15
|
+
this.name = new.target.name;
|
|
16
|
+
this.sessionName = sessionName;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Thrown by {@link create} when a session with the requested name already
|
|
21
|
+
* exists. The substrate never silently adopts an existing session — reconnect
|
|
22
|
+
* to the live session with {@link adopt} instead.
|
|
23
|
+
*/
|
|
24
|
+
export class SessionExists extends ClaudemuxError {
|
|
25
|
+
constructor(sessionName) {
|
|
26
|
+
super("session already exists; refusing to silently adopt — use adopt() to reconnect to a live session", sessionName);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Thrown by {@link create} when a caller-supplied `agentSessionId` is not a
|
|
31
|
+
* well-formed v4 UUID. Thrown *before spawn*, at the substrate boundary.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* This is a **security boundary**, not just input hygiene. The id flows into
|
|
35
|
+
* the agent's argv next to its identity flag, and into the backend's per-session
|
|
36
|
+
* store and command grammar — where, in some backends, a bare `;` element can be
|
|
37
|
+
* a command separator. A v4 UUID is hex + hyphens only, so it can never be `;`,
|
|
38
|
+
* a path, or any token a backend would re-interpret; rejecting non-UUIDs here is
|
|
39
|
+
* what keeps the always-two-argv-elements injection safe. Do not "simplify" this
|
|
40
|
+
* to a loose check.
|
|
41
|
+
*/
|
|
42
|
+
export class InvalidAgentSessionId extends ClaudemuxError {
|
|
43
|
+
/** The malformed value the caller passed. */
|
|
44
|
+
value;
|
|
45
|
+
constructor(value) {
|
|
46
|
+
super(`invalid agentSessionId ${JSON.stringify(value)}: must be a v4 UUID (e.g. the value crypto.randomUUID() produces)`,
|
|
47
|
+
// No session was created — mirror InvalidSessionName's placeholder.
|
|
48
|
+
"<invalid-agentSessionId>");
|
|
49
|
+
this.value = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Thrown by {@link create} when the caller passes an explicit
|
|
54
|
+
* `agentSessionId` **and** an identity flag in `extraArgs` that also selects a
|
|
55
|
+
* conversation id (claude: `--session-id`, `-r`/`--resume`, `--fork-session`).
|
|
56
|
+
* The two would fight over which id the agent runs under, so the substrate
|
|
57
|
+
* fails fast *before spawn* rather than silently dropping one. Pass the id one
|
|
58
|
+
* way or the other, not both.
|
|
59
|
+
*
|
|
60
|
+
* @remarks
|
|
61
|
+
* Which `extraArgs` flags count as identity flags is agent-specific knowledge,
|
|
62
|
+
* so the conflict is detected inside the agent's `buildArgv` (claude owns the
|
|
63
|
+
* flag vocabulary per the layering grep) and surfaced as this neutral error.
|
|
64
|
+
*/
|
|
65
|
+
export class AgentSessionIdConflict extends ClaudemuxError {
|
|
66
|
+
constructor(sessionName) {
|
|
67
|
+
super("explicit agentSessionId conflicts with an identity flag in extraArgs " +
|
|
68
|
+
"(e.g. --session-id / --resume / --fork-session); pass the conversation id one way, not both", sessionName);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Thrown by the boot orchestrator when a recognized dialog matches but its
|
|
73
|
+
* response did not advance the pane within the dialog timeout.
|
|
74
|
+
*/
|
|
75
|
+
export class DialogStuck extends ClaudemuxError {
|
|
76
|
+
/** The matched dialog's id (e.g. `"theme-picker"`). */
|
|
77
|
+
dialogId;
|
|
78
|
+
constructor(sessionName, dialogId) {
|
|
79
|
+
super(`dialog "${dialogId}" matched but did not advance after response`, sessionName);
|
|
80
|
+
this.dialogId = dialogId;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Thrown by **boot** ({@link create}/{@link resume}/{@link adopt}) when the REPL
|
|
85
|
+
* did not reach a stable ready state within `bootTimeoutMs`. NOT thrown by
|
|
86
|
+
* `wait()` — turn patience is the consumer's, so a turn that outlasts your budget
|
|
87
|
+
* is a returned `budget-exceeded` {@link TurnOutcome}, never an exception.
|
|
88
|
+
*/
|
|
89
|
+
export class ReplTimeout extends ClaudemuxError {
|
|
90
|
+
/** The timeout budget that elapsed, in milliseconds. */
|
|
91
|
+
timeoutMs;
|
|
92
|
+
constructor(sessionName, timeoutMs) {
|
|
93
|
+
super(`REPL did not settle within ${timeoutMs}ms`, sessionName);
|
|
94
|
+
this.timeoutMs = timeoutMs;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Thrown by boot when the login-method dialog fires — claudemux assumes the
|
|
99
|
+
* founder is already authenticated, so this dialog firing under claudemux is
|
|
100
|
+
* a setup error, not an auto-answerable dialog.
|
|
101
|
+
*/
|
|
102
|
+
export class LoginRequired extends ClaudemuxError {
|
|
103
|
+
constructor(sessionName) {
|
|
104
|
+
super("claude is not authenticated; the login-method dialog appeared. " +
|
|
105
|
+
"Run `claude` interactively once to sign in, then retry", sessionName);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Thrown by boot when the agent presents a workspace-trust dialog for a
|
|
110
|
+
* `cwd` the substrate has not been told to trust. Trusting a folder is an
|
|
111
|
+
* **authority grant** (the agent gains read/edit/execute on those files),
|
|
112
|
+
* so the substrate fails closed: it does **not** auto-answer the dialog —
|
|
113
|
+
* it throws this *before sending any keystroke* and leaves the decision to
|
|
114
|
+
* the consumer. Pass `trustWorkspace: true` to `create` (or `--trust-workspace`
|
|
115
|
+
* on the CLI) to opt in.
|
|
116
|
+
*
|
|
117
|
+
* @remarks
|
|
118
|
+
* Opting in writes a **persistent, global, per-cwd** trust flag to the
|
|
119
|
+
* agent's config (`~/.claude.json` → `projects[<abs-cwd>]`), NOT a
|
|
120
|
+
* session-scoped one: it outlives the claudemux process and applies to
|
|
121
|
+
* every future `claude` run in that path, including the user's own
|
|
122
|
+
* interactive sessions. Trust is sticky per `(HOME × cwd-path)` — fail-closed
|
|
123
|
+
* only protects the *first* run in an untrusted path; a reused checkout path
|
|
124
|
+
* a prior run (or the user) already trusted inherits trust silently. For
|
|
125
|
+
* untrusted-fork workloads (PR bots / CI), use an ephemeral unique checkout
|
|
126
|
+
* path or an ephemeral HOME per run.
|
|
127
|
+
*/
|
|
128
|
+
export class WorkspaceUntrusted extends ClaudemuxError {
|
|
129
|
+
/** The cwd the agent asked to trust. */
|
|
130
|
+
cwd;
|
|
131
|
+
constructor(sessionName, cwd) {
|
|
132
|
+
super(`workspace at ${JSON.stringify(cwd)} is not trusted; the agent asked to trust it. Pass trustWorkspace:true (or --trust-workspace) to grant the agent read/edit/execute on this folder — note this writes a persistent, per-folder trust flag to the agent's config`, sessionName);
|
|
133
|
+
this.cwd = cwd;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Thrown by {@link SessionHandle.respond} when the agent declares no
|
|
138
|
+
* permission-prompt handling — there is no menu mapping to translate a
|
|
139
|
+
* {@link PromptChoice} into a keystroke, so the substrate refuses to guess a
|
|
140
|
+
* digit (a wrong guess could pick the broadest "allow all" option). An agent
|
|
141
|
+
* grows prompt handling by adding the mapping to its `AgentDef`; until then a
|
|
142
|
+
* consumer that hits an `awaiting{permission-prompt}` must answer the agent
|
|
143
|
+
* out-of-band (or run it in a non-interactive permission mode).
|
|
144
|
+
*/
|
|
145
|
+
export class PromptResponseUnsupported extends ClaudemuxError {
|
|
146
|
+
/** The agent that has no permission-prompt mapping (e.g. a future codex def). */
|
|
147
|
+
agentName;
|
|
148
|
+
constructor(sessionName, agentName) {
|
|
149
|
+
super(`agent "${agentName}" declares no permission-prompt handling; respond() has no menu mapping to answer the prompt`, sessionName);
|
|
150
|
+
this.agentName = agentName;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Thrown by {@link SessionHandle.messagesSince} / {@link SessionHandle.turnComplete}
|
|
155
|
+
* when the session's transcript **cannot be located at all** — there is no
|
|
156
|
+
* recoverable `agentSessionId` to locate it by AND no hook edge has reported its
|
|
157
|
+
* path (an {@link adopt} whose recovery cache missed, a non-claudemux session, or
|
|
158
|
+
* a fork before its first hook edge). Reads are *blind*, not "nothing new."
|
|
159
|
+
*
|
|
160
|
+
* This is a **loud** failure on purpose: an empty read silently conflated with
|
|
161
|
+
* "no reply yet" sits exactly in the crash-recovery re-send path, where the wrong
|
|
162
|
+
* answer double-runs work. Throwing forces the consumer to handle "I can't see
|
|
163
|
+
* this conversation" distinctly. A genuinely empty (but *locatable*) transcript,
|
|
164
|
+
* or an unresolvable cursor (a sentinel/garbage value), still returns empty —
|
|
165
|
+
* only true unlocatability throws.
|
|
166
|
+
*/
|
|
167
|
+
export class TranscriptUnlocatable extends ClaudemuxError {
|
|
168
|
+
constructor(sessionName) {
|
|
169
|
+
super("transcript cannot be located (no recoverable agentSessionId and no hook-reported path); reads are blind, not empty — persist the agentSessionId, or check `agentSessionId !== undefined` before reading", sessionName);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Thrown when the backend cannot find the named session — it has been reaped
|
|
174
|
+
* (a crash, a `kill`, or the box lost its backend server). The canonical "this
|
|
175
|
+
* session is gone" for **every** per-session op (read or write); `kill()` never
|
|
176
|
+
* throws it (killing a gone session is success).
|
|
177
|
+
*/
|
|
178
|
+
export class SessionGone extends ClaudemuxError {
|
|
179
|
+
constructor(sessionName) {
|
|
180
|
+
super("session is gone", sessionName);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Thrown by {@link create} when the agent process **exits before the session
|
|
185
|
+
* becomes ready** — claudemux spawned it, but it was gone (its backend session
|
|
186
|
+
* reaped) before boot could reach an interactive prompt.
|
|
187
|
+
*
|
|
188
|
+
* @remarks
|
|
189
|
+
* **The most common cause is an `agentSessionId` collision:** claude refuses to
|
|
190
|
+
* silently resume or clobber an in-use conversation id — it prints
|
|
191
|
+
* "Session ID … is already in use" and exits. But a malformed `extraArgs` flag,
|
|
192
|
+
* an auth edge, or any startup crash produce the *identical* shape, and
|
|
193
|
+
* claudemux **cannot read which** — the substrate runs panes with
|
|
194
|
+
* `remain-on-exit off`, so claude's stderr is reaped before boot can capture
|
|
195
|
+
* it. That is the same deliberate property that lets {@link adopt} hand back a
|
|
196
|
+
* clean {@link SessionGone} for a crashed agent instead of a corpse handle, so
|
|
197
|
+
* we do not flip it to recover a diagnostic string.
|
|
198
|
+
*
|
|
199
|
+
* This is a distinct class on purpose (errors.ts reuses before adding):
|
|
200
|
+
* {@link SessionGone} reads as *external* reaping of a session that should
|
|
201
|
+
* exist, which does not carry the meaning the create path needs — *"the agent I
|
|
202
|
+
* just spawned rejected its own launch"* (self-inflicted exit vs external
|
|
203
|
+
* interference) — which is exactly what the consumer must act on.
|
|
204
|
+
*
|
|
205
|
+
* When the spawn used a caller-chosen id, it is carried on
|
|
206
|
+
* {@link agentSessionId} so the collision case stays actionable as structured
|
|
207
|
+
* data (pick another id, or resume that conversation) without scraping text.
|
|
208
|
+
*
|
|
209
|
+
* **Deferred precision:** once a `transcriptPath` helper lands (owning claude's
|
|
210
|
+
* cwd-slug rule in `claude.ts` per the layering grep), a pre-spawn transcript
|
|
211
|
+
* probe can upgrade the collision case to a precise `AgentSessionInUse` and
|
|
212
|
+
* fall back to this error for the other death causes — a non-breaking addition.
|
|
213
|
+
*/
|
|
214
|
+
export class AgentExitedDuringBoot extends ClaudemuxError {
|
|
215
|
+
/** The caller-chosen id the spawn used, if any — the likely collision culprit. */
|
|
216
|
+
agentSessionId;
|
|
217
|
+
constructor(sessionName, agentSessionId) {
|
|
218
|
+
const withId = agentSessionId !== undefined
|
|
219
|
+
? ` (spawned with agentSessionId ${agentSessionId}, which is most likely already in use)`
|
|
220
|
+
: "";
|
|
221
|
+
super(`the agent exited before the session became ready${withId}; the most common cause is an agentSessionId collision (the agent refuses to resume silently and exits)`, sessionName);
|
|
222
|
+
if (agentSessionId !== undefined)
|
|
223
|
+
this.agentSessionId = agentSessionId;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Thrown when the underlying backend (the agent's I/O substrate) is
|
|
228
|
+
* unreachable — the backend process failed to spawn (`spawn-failed`), its
|
|
229
|
+
* server was not running on the requested connection (`no-server`), or a
|
|
230
|
+
* backend invocation hung past its budget (`timeout`).
|
|
231
|
+
*/
|
|
232
|
+
export class BackendUnreachable extends ClaudemuxError {
|
|
233
|
+
/** Why the backend was unreachable — see {@link BackendUnreachableKind}. */
|
|
234
|
+
kind;
|
|
235
|
+
/** The underlying spawn / connection error, if available. */
|
|
236
|
+
underlying;
|
|
237
|
+
constructor(sessionName, kind, underlying) {
|
|
238
|
+
super(`backend unreachable [${kind}]${underlying ? ` (${underlying.message})` : ""}`, sessionName);
|
|
239
|
+
this.kind = kind;
|
|
240
|
+
if (underlying !== undefined) {
|
|
241
|
+
this.underlying = underlying;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Thrown by `create` / `spawn` when the session name or namespace contains
|
|
247
|
+
* characters that the substrate cannot encode safely for the backend's
|
|
248
|
+
* target grammar. The substrate fails fast at the boundary instead of
|
|
249
|
+
* silently renaming and producing an un-addressable handle.
|
|
250
|
+
*
|
|
251
|
+
* @remarks
|
|
252
|
+
* Reserved set: `.`, `:`, `*`, `?`, leading `-`, any whitespace, `/`,
|
|
253
|
+
* `\n`, `\r`, `\\`, and the empty string.
|
|
254
|
+
*/
|
|
255
|
+
export class InvalidSessionName extends ClaudemuxError {
|
|
256
|
+
/** Which field was invalid (`"name"` or `"namespace"`). */
|
|
257
|
+
field;
|
|
258
|
+
/** The actual value the caller passed. */
|
|
259
|
+
value;
|
|
260
|
+
/** The reason the value is rejected. */
|
|
261
|
+
reason;
|
|
262
|
+
constructor(field, value, reason) {
|
|
263
|
+
super(`invalid ${field} ${JSON.stringify(value)}: ${reason}`,
|
|
264
|
+
// Use the offending value itself so the error has *some* identifier.
|
|
265
|
+
// The session was never created; there is no real name to use.
|
|
266
|
+
`<invalid-${field}>`);
|
|
267
|
+
this.field = field;
|
|
268
|
+
this.value = value;
|
|
269
|
+
this.reason = reason;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Wrapper for an unexpected backend failure that isn't one of the typed
|
|
274
|
+
* cases above (non-zero exit + unrecognized stderr).
|
|
275
|
+
*
|
|
276
|
+
* @remarks
|
|
277
|
+
* The `.message` deliberately **excludes the backend argv** — the argv is
|
|
278
|
+
* pure backend vocabulary (the backend's own subcommand names) and leaking
|
|
279
|
+
* it into the user-facing message violates the substrate's "zero references
|
|
280
|
+
* to the backend in error messages" promise. The argv lives on `.argv` (and
|
|
281
|
+
* flows through `onBackendCommand`) for programmatic diagnosis; the
|
|
282
|
+
* human-readable message carries the exit code and the backend's own stderr
|
|
283
|
+
* text, which is the diagnostic value without the command vocabulary.
|
|
284
|
+
*
|
|
285
|
+
* This is the structural backstop behind the backend classifier's
|
|
286
|
+
* routine-case promotion: even a backend failure shape we haven't classified
|
|
287
|
+
* yet cannot leak the backend's command names into user-facing text.
|
|
288
|
+
*/
|
|
289
|
+
export class BackendError extends ClaudemuxError {
|
|
290
|
+
argv;
|
|
291
|
+
exitCode;
|
|
292
|
+
stderr;
|
|
293
|
+
constructor(sessionName, argv, exitCode, stderr) {
|
|
294
|
+
super(`backend command failed (exit ${exitCode}): ${stderr.trim() || "<empty stderr>"}`, sessionName);
|
|
295
|
+
this.argv = argv;
|
|
296
|
+
this.exitCode = exitCode;
|
|
297
|
+
this.stderr = stderr;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,4EAA4E;AAC5E,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,0CAA0C;IACjC,WAAW,CAAS;IAE7B,YAAY,OAAe,EAAE,WAAmB;QAC9C,KAAK,CAAC,cAAc,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,aAAc,SAAQ,cAAc;IAC/C,YAAY,WAAmB;QAC7B,KAAK,CACH,iGAAiG,EACjG,WAAW,CACZ,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,qBAAsB,SAAQ,cAAc;IACvD,6CAA6C;IACpC,KAAK,CAAS;IAEvB,YAAY,KAAa;QACvB,KAAK,CACH,0BAA0B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,mEAAmE;QAClH,oEAAoE;QACpE,0BAA0B,CAC3B,CAAC;QACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,sBAAuB,SAAQ,cAAc;IACxD,YAAY,WAAmB;QAC7B,KAAK,CACH,uEAAuE;YACrE,6FAA6F,EAC/F,WAAW,CACZ,CAAC;IACJ,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,cAAc;IAC7C,uDAAuD;IAC9C,QAAQ,CAAS;IAE1B,YAAY,WAAmB,EAAE,QAAgB;QAC/C,KAAK,CAAC,WAAW,QAAQ,8CAA8C,EAAE,WAAW,CAAC,CAAC;QACtF,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,WAAY,SAAQ,cAAc;IAC7C,wDAAwD;IAC/C,SAAS,CAAS;IAE3B,YAAY,WAAmB,EAAE,SAAiB;QAChD,KAAK,CAAC,8BAA8B,SAAS,IAAI,EAAE,WAAW,CAAC,CAAC;QAChE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,aAAc,SAAQ,cAAc;IAC/C,YAAY,WAAmB;QAC7B,KAAK,CACH,iEAAiE;YAC/D,wDAAwD,EAC1D,WAAW,CACZ,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IACpD,wCAAwC;IAC/B,GAAG,CAAS;IAErB,YAAY,WAAmB,EAAE,GAAW;QAC1C,KAAK,CACH,gBAAgB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,gOAAgO,EACnQ,WAAW,CACZ,CAAC;QACF,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,yBAA0B,SAAQ,cAAc;IAC3D,iFAAiF;IACxE,SAAS,CAAS;IAE3B,YAAY,WAAmB,EAAE,SAAiB;QAChD,KAAK,CACH,UAAU,SAAS,8FAA8F,EACjH,WAAW,CACZ,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,qBAAsB,SAAQ,cAAc;IACvD,YAAY,WAAmB;QAC7B,KAAK,CACH,yMAAyM,EACzM,WAAW,CACZ,CAAC;IACJ,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,WAAY,SAAQ,cAAc;IAC7C,YAAY,WAAmB;QAC7B,KAAK,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;IACxC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,OAAO,qBAAsB,SAAQ,cAAc;IACvD,kFAAkF;IACzE,cAAc,CAAU;IAEjC,YAAY,WAAmB,EAAE,cAAuB;QACtD,MAAM,MAAM,GACV,cAAc,KAAK,SAAS;YAC1B,CAAC,CAAC,iCAAiC,cAAc,wCAAwC;YACzF,CAAC,CAAC,EAAE,CAAC;QACT,KAAK,CACH,mDAAmD,MAAM,yGAAyG,EAClK,WAAW,CACZ,CAAC;QACF,IAAI,cAAc,KAAK,SAAS;YAAE,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACzE,CAAC;CACF;AAYD;;;;;GAKG;AACH,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IACpD,4EAA4E;IACnE,IAAI,CAAyB;IACtC,6DAA6D;IACpD,UAAU,CAAS;IAE5B,YAAY,WAAmB,EAAE,IAA4B,EAAE,UAAkB;QAC/E,KAAK,CACH,wBAAwB,IAAI,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAC9E,WAAW,CACZ,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC/B,CAAC;IACH,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IACpD,2DAA2D;IAClD,KAAK,CAAuB;IACrC,0CAA0C;IACjC,KAAK,CAAS;IACvB,wCAAwC;IAC/B,MAAM,CAAS;IAExB,YAAY,KAA2B,EAAE,KAAa,EAAE,MAAc;QACpE,KAAK,CACH,WAAW,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,MAAM,EAAE;QACtD,qEAAqE;QACrE,+DAA+D;QAC/D,YAAY,KAAK,GAAG,CACrB,CAAC;QACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,YAAa,SAAQ,cAAc;IACrC,IAAI,CAAoB;IACxB,QAAQ,CAAS;IACjB,MAAM,CAAS;IAExB,YAAY,WAAmB,EAAE,IAAuB,EAAE,QAAgB,EAAE,MAAc;QACxF,KAAK,CACH,gCAAgC,QAAQ,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,gBAAgB,EAAE,EACjF,WAAW,CACZ,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claudemux — drive long-lived Claude Code sessions from Node.
|
|
3
|
+
*
|
|
4
|
+
* Public re-exports only; the file carries no logic. Internal seams
|
|
5
|
+
* (`Backend`, `BackendEvent`, `SendPayload`, `ClassifierRules`) stay
|
|
6
|
+
* internal so the public API survives a backend swap without consumer
|
|
7
|
+
* rewrites.
|
|
8
|
+
*/
|
|
9
|
+
export { claude } from "./agents/index.js";
|
|
10
|
+
export type { AgentDef, BootDialog, HookEdge } from "./agents/types.js";
|
|
11
|
+
export { AgentExitedDuringBoot, AgentSessionIdConflict, BackendError, BackendUnreachable, ClaudemuxError, DialogStuck, InvalidAgentSessionId, InvalidSessionName, LoginRequired, PromptResponseUnsupported, ReplTimeout, SessionExists, SessionGone, TranscriptUnlocatable, WorkspaceUntrusted, } from "./errors.js";
|
|
12
|
+
export { ask, recover } from "./compose.js";
|
|
13
|
+
export type { AskResult, RecoverResult, RecoverStatus } from "./compose.js";
|
|
14
|
+
export { DELIVERED_QUEUED, DELIVERY_UNCONFIRMED } from "./session/handle.js";
|
|
15
|
+
export { adopt } from "./session/adopt.js";
|
|
16
|
+
export type { AdoptOptions } from "./session/adopt.js";
|
|
17
|
+
export { create } from "./session/create.js";
|
|
18
|
+
export { exists, kill, list } from "./session/registry.js";
|
|
19
|
+
export { resume } from "./session/resume.js";
|
|
20
|
+
export type { ResumeOptions } from "./session/resume.js";
|
|
21
|
+
export type { BackendCommandEvent, IdleState, Message, MessagePart, Progress, PromptChoice, ReadyOpts, SessionHandle, State, TurnOutcome, } from "./types.js";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,yBAAyB,EACzB,WAAW,EACX,aAAa,EACb,WAAW,EACX,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,YAAY,EACV,mBAAmB,EACnB,SAAS,EACT,OAAO,EACP,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,aAAa,EACb,KAAK,EACL,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claudemux — drive long-lived Claude Code sessions from Node.
|
|
3
|
+
*
|
|
4
|
+
* Public re-exports only; the file carries no logic. Internal seams
|
|
5
|
+
* (`Backend`, `BackendEvent`, `SendPayload`, `ClassifierRules`) stay
|
|
6
|
+
* internal so the public API survives a backend swap without consumer
|
|
7
|
+
* rewrites.
|
|
8
|
+
*/
|
|
9
|
+
export { claude } from "./agents/index.js";
|
|
10
|
+
export { AgentExitedDuringBoot, AgentSessionIdConflict, BackendError, BackendUnreachable, ClaudemuxError, DialogStuck, InvalidAgentSessionId, InvalidSessionName, LoginRequired, PromptResponseUnsupported, ReplTimeout, SessionExists, SessionGone, TranscriptUnlocatable, WorkspaceUntrusted, } from "./errors.js";
|
|
11
|
+
export { ask, recover } from "./compose.js";
|
|
12
|
+
export { DELIVERED_QUEUED, DELIVERY_UNCONFIRMED } from "./session/handle.js";
|
|
13
|
+
export { adopt } from "./session/adopt.js";
|
|
14
|
+
export { create } from "./session/create.js";
|
|
15
|
+
export { exists, kill, list } from "./session/registry.js";
|
|
16
|
+
export { resume } from "./session/resume.js";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,yBAAyB,EACzB,WAAW,EACX,aAAa,EACb,WAAW,EACX,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AgentDef } from "../agents/types.js";
|
|
2
|
+
import type { Backend, SessionRef } from "../backends/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The send→wait *baseline* — how a stateless `wait()` (the CLI reattaches in a
|
|
5
|
+
* fresh process each invocation) tells a turn that already completed from the
|
|
6
|
+
* previous turn's idle prompt.
|
|
7
|
+
*
|
|
8
|
+
* `wait()` is transition-aware: it will not accept `idle` as "turn complete"
|
|
9
|
+
* until it has seen the pane *leave* idle. In-process that signal is an
|
|
10
|
+
* observed `working` frame. But the CLI `send` and `wait` are separate
|
|
11
|
+
* processes; for a fast turn the agent can be back to idle before the `wait`
|
|
12
|
+
* process takes its first capture, so `wait` never observes `working` and
|
|
13
|
+
* hangs to `ReplTimeout` (bug 8a500a52).
|
|
14
|
+
*
|
|
15
|
+
* The fix: `send` records a fingerprint of the **post-submit** pane — the
|
|
16
|
+
* frame after the input box has cleared but *before* the agent's answer lands
|
|
17
|
+
* — under a session-scoped key. A later `wait` arms when the live pane
|
|
18
|
+
* *diverges* from that fingerprint. Capturing the post-submit frame (not the
|
|
19
|
+
* pre-send one) is what keeps the previous turn's idle from counting as a
|
|
20
|
+
* divergence: during the post-submit window the live pane *equals* the
|
|
21
|
+
* baseline, so `wait` correctly keeps polling instead of returning early.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SEND_BASELINE_KEY = "send-baseline";
|
|
24
|
+
/** Stable fingerprint of a bottom-N pane capture (sha256 hex — newline-safe). */
|
|
25
|
+
export declare function paneFingerprint(text: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* After a `send`, capture the post-submit pane fingerprint: the first frame
|
|
28
|
+
* that (a) differs from the pre-send pane `pre` and (b) is a settled idle box
|
|
29
|
+
* or a working frame — i.e. the submit was accepted and the box cleared (or
|
|
30
|
+
* the agent already started working), but *before* the answer completes.
|
|
31
|
+
*
|
|
32
|
+
* Returns `undefined` when no clean frame appears within budget, or when the
|
|
33
|
+
* pre-send pane is unknown (without `pre` we cannot exclude the previous idle,
|
|
34
|
+
* and a wrong baseline would re-introduce the premature-return race). In that
|
|
35
|
+
* case `wait` falls back to arming on an observed `working` frame.
|
|
36
|
+
*/
|
|
37
|
+
export declare function captureSendBaseline(backend: Backend, agent: AgentDef, ref: SessionRef, pre: string | undefined): Promise<string | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Read the persisted post-submit baseline fingerprint, if any. Best-effort:
|
|
40
|
+
* returns `undefined` when unset or unreadable, so `wait` degrades to
|
|
41
|
+
* observed-working arming rather than failing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readSendBaseline(backend: Backend, ref: SessionRef): Promise<string | undefined>;
|
|
44
|
+
/**
|
|
45
|
+
* Persist the post-submit baseline fingerprint for a later `wait`. An empty
|
|
46
|
+
* `fingerprint` clears the baseline (so `readSendBaseline` reports "unset") —
|
|
47
|
+
* `send` uses that to drop a prior turn's stale fingerprint when it couldn't
|
|
48
|
+
* establish a fresh one. Best-effort — a metadata write failure must never
|
|
49
|
+
* fail the `send` (whose contract is byte delivery); `wait` then falls back to
|
|
50
|
+
* observed-working arming.
|
|
51
|
+
*/
|
|
52
|
+
export declare function writeSendBaseline(backend: Backend, ref: SessionRef, fingerprint: string): Promise<void>;
|
|
53
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/io/baseline.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIhE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAOjD,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoB7B;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,UAAU,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { CLASSIFIER_CAPTURE } from "../session/constants.js";
|
|
3
|
+
import { sleep } from "../util/sleep.js";
|
|
4
|
+
/**
|
|
5
|
+
* The send→wait *baseline* — how a stateless `wait()` (the CLI reattaches in a
|
|
6
|
+
* fresh process each invocation) tells a turn that already completed from the
|
|
7
|
+
* previous turn's idle prompt.
|
|
8
|
+
*
|
|
9
|
+
* `wait()` is transition-aware: it will not accept `idle` as "turn complete"
|
|
10
|
+
* until it has seen the pane *leave* idle. In-process that signal is an
|
|
11
|
+
* observed `working` frame. But the CLI `send` and `wait` are separate
|
|
12
|
+
* processes; for a fast turn the agent can be back to idle before the `wait`
|
|
13
|
+
* process takes its first capture, so `wait` never observes `working` and
|
|
14
|
+
* hangs to `ReplTimeout` (bug 8a500a52).
|
|
15
|
+
*
|
|
16
|
+
* The fix: `send` records a fingerprint of the **post-submit** pane — the
|
|
17
|
+
* frame after the input box has cleared but *before* the agent's answer lands
|
|
18
|
+
* — under a session-scoped key. A later `wait` arms when the live pane
|
|
19
|
+
* *diverges* from that fingerprint. Capturing the post-submit frame (not the
|
|
20
|
+
* pre-send one) is what keeps the previous turn's idle from counting as a
|
|
21
|
+
* divergence: during the post-submit window the live pane *equals* the
|
|
22
|
+
* baseline, so `wait` correctly keeps polling instead of returning early.
|
|
23
|
+
*/
|
|
24
|
+
export const SEND_BASELINE_KEY = "send-baseline";
|
|
25
|
+
/** Total budget for `send` to observe the post-submit frame before giving up. */
|
|
26
|
+
const BASELINE_CAPTURE_BUDGET_MS = 2_000;
|
|
27
|
+
/** Capture cadence while watching for the post-submit frame. */
|
|
28
|
+
const BASELINE_POLL_MS = 40;
|
|
29
|
+
/** Stable fingerprint of a bottom-N pane capture (sha256 hex — newline-safe). */
|
|
30
|
+
export function paneFingerprint(text) {
|
|
31
|
+
return createHash("sha256").update(text).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* After a `send`, capture the post-submit pane fingerprint: the first frame
|
|
35
|
+
* that (a) differs from the pre-send pane `pre` and (b) is a settled idle box
|
|
36
|
+
* or a working frame — i.e. the submit was accepted and the box cleared (or
|
|
37
|
+
* the agent already started working), but *before* the answer completes.
|
|
38
|
+
*
|
|
39
|
+
* Returns `undefined` when no clean frame appears within budget, or when the
|
|
40
|
+
* pre-send pane is unknown (without `pre` we cannot exclude the previous idle,
|
|
41
|
+
* and a wrong baseline would re-introduce the premature-return race). In that
|
|
42
|
+
* case `wait` falls back to arming on an observed `working` frame.
|
|
43
|
+
*/
|
|
44
|
+
export async function captureSendBaseline(backend, agent, ref, pre) {
|
|
45
|
+
if (pre === undefined)
|
|
46
|
+
return undefined;
|
|
47
|
+
const deadline = Date.now() + BASELINE_CAPTURE_BUDGET_MS;
|
|
48
|
+
while (Date.now() < deadline) {
|
|
49
|
+
let text;
|
|
50
|
+
try {
|
|
51
|
+
text = await backend.capture(ref, CLASSIFIER_CAPTURE);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined; // pane unreadable — let wait fall back to working-arm
|
|
55
|
+
}
|
|
56
|
+
// The first frame that has changed from the pre-send pane AND is either an
|
|
57
|
+
// empty input box (the submit cleared it) or a working frame. This is
|
|
58
|
+
// reached before the answer lands, so it never captures the completed-turn
|
|
59
|
+
// pane (which would make a later wait see "no divergence" and hang).
|
|
60
|
+
if (text !== pre && (agent.boot.isReady(text) || agent.rules.working(text))) {
|
|
61
|
+
return paneFingerprint(text);
|
|
62
|
+
}
|
|
63
|
+
await sleep(BASELINE_POLL_MS);
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read the persisted post-submit baseline fingerprint, if any. Best-effort:
|
|
69
|
+
* returns `undefined` when unset or unreadable, so `wait` degrades to
|
|
70
|
+
* observed-working arming rather than failing.
|
|
71
|
+
*/
|
|
72
|
+
export async function readSendBaseline(backend, ref) {
|
|
73
|
+
try {
|
|
74
|
+
const v = await backend.getSessionMeta(ref, SEND_BASELINE_KEY);
|
|
75
|
+
return v && v.length > 0 ? v : undefined;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Persist the post-submit baseline fingerprint for a later `wait`. An empty
|
|
83
|
+
* `fingerprint` clears the baseline (so `readSendBaseline` reports "unset") —
|
|
84
|
+
* `send` uses that to drop a prior turn's stale fingerprint when it couldn't
|
|
85
|
+
* establish a fresh one. Best-effort — a metadata write failure must never
|
|
86
|
+
* fail the `send` (whose contract is byte delivery); `wait` then falls back to
|
|
87
|
+
* observed-working arming.
|
|
88
|
+
*/
|
|
89
|
+
export async function writeSendBaseline(backend, ref, fingerprint) {
|
|
90
|
+
try {
|
|
91
|
+
await backend.setSessionMeta(ref, SEND_BASELINE_KEY, fingerprint);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* baseline is an optimization for cross-process wait; ignore */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/io/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAEjD,iFAAiF;AACjF,MAAM,0BAA0B,GAAG,KAAK,CAAC;AACzC,gEAAgE;AAChE,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAgB,EAChB,KAAe,EACf,GAAe,EACf,GAAuB;IAEvB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,0BAA0B,CAAC;IACzD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC,CAAC,sDAAsD;QAC1E,CAAC;QACD,2EAA2E;QAC3E,sEAAsE;QACtE,2EAA2E;QAC3E,qEAAqE;QACrE,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC5E,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QACD,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgB,EAChB,GAAe;IAEf,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgB,EAChB,GAAe,EACf,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,iBAAiB,EAAE,WAAW,CAAC,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,gEAAgE;IAClE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Backend, SessionRef } from "../backends/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pure pass-through to `Backend.capture`. Lives in `src/io/` because the
|
|
4
|
+
* top-level public verbs ARE the io layer; the backend is below it. Future
|
|
5
|
+
* non-tmux backends implement the same `capture` shape, so the wrapper
|
|
6
|
+
* stays trivial.
|
|
7
|
+
*
|
|
8
|
+
* @param opts.ansi preserve escape sequences when `true`.
|
|
9
|
+
* @param opts.lines bottom-N visible lines (default: full visible region).
|
|
10
|
+
*/
|
|
11
|
+
export declare function captureOnce(backend: Backend, ref: SessionRef, opts?: {
|
|
12
|
+
ansi?: boolean;
|
|
13
|
+
lines?: number;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/io/capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,UAAU,EACf,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CAEjB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure pass-through to `Backend.capture`. Lives in `src/io/` because the
|
|
3
|
+
* top-level public verbs ARE the io layer; the backend is below it. Future
|
|
4
|
+
* non-tmux backends implement the same `capture` shape, so the wrapper
|
|
5
|
+
* stays trivial.
|
|
6
|
+
*
|
|
7
|
+
* @param opts.ansi preserve escape sequences when `true`.
|
|
8
|
+
* @param opts.lines bottom-N visible lines (default: full visible region).
|
|
9
|
+
*/
|
|
10
|
+
export function captureOnce(backend, ref, opts) {
|
|
11
|
+
return backend.capture(ref, opts);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/io/capture.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,OAAgB,EAChB,GAAe,EACf,IAAyC;IAEzC,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Backend, SessionRef } from "../backends/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fire a single `Escape` at the pane — claude's own documented interrupt key
|
|
4
|
+
* (the classifier detects `working` by the literal `"esc to interrupt"`
|
|
5
|
+
* affordance, `agents/claude.ts`). ESC is already in `SendPayload`'s key union
|
|
6
|
+
* (`backends/types.ts`) and `sendKey` already sends it, so there is no backend
|
|
7
|
+
* change.
|
|
8
|
+
*
|
|
9
|
+
* ESC is sent **unconditionally** — no state-check guard. A guard would bake
|
|
10
|
+
* policy into the substrate and open a TOCTOU race (state read, then ESC, with
|
|
11
|
+
* the agent free to change state in between). ESC on an idle claude is harmless
|
|
12
|
+
* (it clears the input box). Gating on `state()` is the consumer's call. This
|
|
13
|
+
* is a mechanism, not a policy
|
|
14
|
+
* (`brain/decisions/0013-mechanism-not-policy-substrate-boundary.md`).
|
|
15
|
+
*
|
|
16
|
+
* That consumer-side gate is also not atomic with the ESC: a turn can finish
|
|
17
|
+
* between a `state()===working` read and the ESC landing — most easily across
|
|
18
|
+
* separate CLI processes (a short turn completes in the gap), so the ESC hits
|
|
19
|
+
* an already-idle agent. That is a harmless no-op, not a failure; a consumer
|
|
20
|
+
* that needs the interrupt to catch a turn should read `state()` and call this
|
|
21
|
+
* in one tight in-process sequence, not trust a stale prior-process reading.
|
|
22
|
+
*
|
|
23
|
+
* Blocks on **write delivery** plus a brief fixed settle ({@link
|
|
24
|
+
* INTERRUPT_SETTLE_MS}); it guarantees ESC was delivered, NOT that an in-flight
|
|
25
|
+
* abort has fully completed. This verb does exactly one named action — stop the
|
|
26
|
+
* turn — and nothing more (`brain/decisions/0013`, "a primitive does exactly the
|
|
27
|
+
* keystroke it names").
|
|
28
|
+
*
|
|
29
|
+
* **After interrupt(), claude does NOT return to a clean idle prompt.** It
|
|
30
|
+
* restores the interrupted message back into the composer, and the classifier
|
|
31
|
+
* reads that frame as `unknown` (never `idle`, never `working`). Two
|
|
32
|
+
* consequences the consumer must know:
|
|
33
|
+
* - `wait()` after interrupt() resolves `{ kind: "aborted" }` immediately (the
|
|
34
|
+
* handle records the interrupt authoritatively) — it does NOT hang waiting for
|
|
35
|
+
* an idle that won't come.
|
|
36
|
+
* - Do **not** naively `send()` a replacement after interrupt(): `send`
|
|
37
|
+
* pastes into the *non-empty* composer (the restored message), so the
|
|
38
|
+
* submission is the two texts concatenated. For a clean "interrupt and
|
|
39
|
+
* replace" the composer must first be cleared to empty. claude's only
|
|
40
|
+
* substrate-reachable composer clear is repeated ESC (its "Esc again to
|
|
41
|
+
* clear" ladder), so the recipe is consumer-composed and claude-specific —
|
|
42
|
+
* see the README "Interrupting a working agent" note. It is deliberately
|
|
43
|
+
* NOT bundled into this agent-agnostic verb.
|
|
44
|
+
*/
|
|
45
|
+
export declare function interruptOnce(backend: Backend, ref: SessionRef): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=interrupt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interrupt.d.ts","sourceRoot":"","sources":["../../src/io/interrupt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAehE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpF"}
|