@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,86 @@
|
|
|
1
|
+
import type { AgentDef, HookEdge } from "../agents/types.js";
|
|
2
|
+
import type { Progress, State } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Derive a {@link Progress} from the ordered hook edges + a transcript count.
|
|
5
|
+
* Pure — the testable heart of the Observer.
|
|
6
|
+
*
|
|
7
|
+
* `phase`: `stop` ⇒ `done`; else a tool in flight ⇒ `tool`; else the last edge
|
|
8
|
+
* being `tool-end` ⇒ `composing`; else ⇒ `prompt`; no edges ⇒ `unknown`.
|
|
9
|
+
* `toolInFlight`: net-open tools (`tool-start` count minus `tool-end` count).
|
|
10
|
+
*/
|
|
11
|
+
export declare function deriveProgress(o: {
|
|
12
|
+
edges: readonly HookEdge[];
|
|
13
|
+
transcriptCount: number;
|
|
14
|
+
}): Progress;
|
|
15
|
+
/**
|
|
16
|
+
* The single fused belief about a session **right now** — the one owner of
|
|
17
|
+
* "what's true." Extends {@link Progress} with the pane-only facts hooks can't
|
|
18
|
+
* see (`interrupted`) and the edge timings `wait()` composes into a
|
|
19
|
+
* {@link import('../types.js').TurnOutcome} (`lastStopAt` = the turn-end trigger,
|
|
20
|
+
* `lastActivityAt` = a progress heartbeat). Pure: the caller supplies the
|
|
21
|
+
* already-classified pane (classification is the agent's job, fusion is ours).
|
|
22
|
+
*/
|
|
23
|
+
export interface Belief extends Progress {
|
|
24
|
+
/** The pane shows an interrupted (ESC'd) turn — `wait()` maps this to `aborted`. */
|
|
25
|
+
readonly interrupted: boolean;
|
|
26
|
+
/** ms of the most recent turn-end (`stop`) edge, if any. */
|
|
27
|
+
readonly lastStopAt?: number;
|
|
28
|
+
/** ms of the most recent lifecycle edge of any kind — a liveness heartbeat. */
|
|
29
|
+
readonly lastActivityAt?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Edges from the **current** session lifecycle only: everything from the latest
|
|
33
|
+
* `session-start` onward. The rendezvous is keyed by conversation id and *reused
|
|
34
|
+
* across resume*, so a crashed prior life leaves an unclosed `prompt-submit` (and
|
|
35
|
+
* a stale `session-start`) in the same file. Computing the belief over those
|
|
36
|
+
* poisons it (the false-`working`-after-resume bug). Resetting at the last
|
|
37
|
+
* `session-start` boundary is the one fix for that whole family. Edges are sorted
|
|
38
|
+
* by time, so the last `session-start` in the array is the most recent; with no
|
|
39
|
+
* `session-start` at all (a bare progress sequence) everything is kept.
|
|
40
|
+
*/
|
|
41
|
+
export declare function currentLifeEdges(edges: readonly HookEdge[]): readonly HookEdge[];
|
|
42
|
+
/**
|
|
43
|
+
* Fuse the reliable signals + the pre-classified pane into one {@link Belief}.
|
|
44
|
+
* `state` precedence: pane-only modals (dialog/permission) → hook lifecycle
|
|
45
|
+
* (when the channel is live and a turn has happened) → pane (hooks silent/off).
|
|
46
|
+
* The hook-lifecycle branch carries one pane cross-check for its blind spot: a
|
|
47
|
+
* denied/abandoned tool leaves a dangling `tool-start` (no `tool-end`), so a
|
|
48
|
+
* settled idle pane overrides a stuck hook `working`. Only {@link currentLifeEdges}
|
|
49
|
+
* feed the belief, so a resumed session is never judged by its prior life's edges.
|
|
50
|
+
*/
|
|
51
|
+
export declare function believe(o: {
|
|
52
|
+
edges: readonly HookEdge[];
|
|
53
|
+
transcriptCount: number;
|
|
54
|
+
/**
|
|
55
|
+
* The pre-classified pane. `nonEmpty` (does the captured frame carry any real,
|
|
56
|
+
* non-whitespace content) feeds the {@link Belief.agentChannelHealthy} drift
|
|
57
|
+
* canary — we only judge "all channels blind" against a pane that actually has
|
|
58
|
+
* content. Optional: absent ⇒ treated as empty (no drift judgment).
|
|
59
|
+
*/
|
|
60
|
+
pane: {
|
|
61
|
+
state: State;
|
|
62
|
+
interrupted: boolean;
|
|
63
|
+
nonEmpty?: boolean;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Authoritative "this handle issued an interrupt not yet superseded by a send."
|
|
67
|
+
* An interrupt fires NO `stop` edge AND leaves the spinner's `esc to interrupt`
|
|
68
|
+
* frozen in scrollback (so the pane mis-classifies as `working`) — neither
|
|
69
|
+
* channel can tell a frozen spinner from a live one. The handle KNOWS, so this
|
|
70
|
+
* flag overrides both. The pane's "Interrupted" text is only a best-effort
|
|
71
|
+
* fallback for a *human* interrupt we didn't issue.
|
|
72
|
+
*/
|
|
73
|
+
weInterrupted?: boolean;
|
|
74
|
+
}): Belief;
|
|
75
|
+
/**
|
|
76
|
+
* Read the hook rendezvous into ordered {@link HookEdge}s (chronological).
|
|
77
|
+
* Empty when hooks are off, the agent has no hook spec, or the file is absent
|
|
78
|
+
* — degrades, never throws. A FULL read: used by `bootSession` (a one-shot, not
|
|
79
|
+
* a hot path); the per-poll session path is the incremental
|
|
80
|
+
* {@link import('./session-observer.js').SessionObserver}.
|
|
81
|
+
*/
|
|
82
|
+
export declare function readHookEdges(o: {
|
|
83
|
+
agent: AgentDef;
|
|
84
|
+
rendezvousPath: string;
|
|
85
|
+
}): HookEdge[];
|
|
86
|
+
//# sourceMappingURL=observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer.d.ts","sourceRoot":"","sources":["../../src/observe/observer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAwBnD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE;IAChC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,QAAQ,CAyCX;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,MAAO,SAAQ,QAAQ;IACtC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,QAAQ,EAAE,CAMhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE;IACzB,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACjE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,GAAG,MAAM,CA6CT;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,EAAE,CAUxF"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* The Observer — **agent-agnostic** fusion of the reliable observe signals into
|
|
4
|
+
* one belief. It knows nothing of any agent's transcript schema or hook
|
|
5
|
+
* vocabulary: it reads files by path and delegates every agent-specific bit to
|
|
6
|
+
* {@link AgentDef.transcript} / {@link AgentDef.hooks}, and takes the pane only
|
|
7
|
+
* as a pre-classified verdict. (grep-enforced: no jsonl/claude vocabulary here.)
|
|
8
|
+
*
|
|
9
|
+
* **Single owner of "what's true."** `state()` and `progress()` both defer to
|
|
10
|
+
* {@link believe}; `wait()` composes that belief with the patience policy. No
|
|
11
|
+
* caller forms its own belief from raw signals. Fusion precedence: pane-only
|
|
12
|
+
* modal states (dialog/permission) win (hooks can't see them) → else the hook
|
|
13
|
+
* lifecycle (the reliable channel) → else the pane (hooks silent/off).
|
|
14
|
+
*/
|
|
15
|
+
function readLines(path) {
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(path, "utf8").split("\n");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return []; // absent/unreadable file → no signal (degrades, never throws)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Derive a {@link Progress} from the ordered hook edges + a transcript count.
|
|
25
|
+
* Pure — the testable heart of the Observer.
|
|
26
|
+
*
|
|
27
|
+
* `phase`: `stop` ⇒ `done`; else a tool in flight ⇒ `tool`; else the last edge
|
|
28
|
+
* being `tool-end` ⇒ `composing`; else ⇒ `prompt`; no edges ⇒ `unknown`.
|
|
29
|
+
* `toolInFlight`: net-open tools (`tool-start` count minus `tool-end` count).
|
|
30
|
+
*/
|
|
31
|
+
export function deriveProgress(o) {
|
|
32
|
+
let open = 0;
|
|
33
|
+
for (const e of o.edges) {
|
|
34
|
+
if (e.event === "tool-start")
|
|
35
|
+
open += 1;
|
|
36
|
+
else if (e.event === "tool-end")
|
|
37
|
+
open = Math.max(0, open - 1);
|
|
38
|
+
}
|
|
39
|
+
const toolInFlight = open > 0;
|
|
40
|
+
const lifecycle = o.edges.filter((e) => e.event === "prompt-submit" ||
|
|
41
|
+
e.event === "tool-start" ||
|
|
42
|
+
e.event === "tool-end" ||
|
|
43
|
+
e.event === "stop");
|
|
44
|
+
const last = lifecycle[lifecycle.length - 1];
|
|
45
|
+
let phase;
|
|
46
|
+
if (last === undefined)
|
|
47
|
+
phase = "unknown";
|
|
48
|
+
else if (last.event === "stop")
|
|
49
|
+
phase = "done";
|
|
50
|
+
else if (toolInFlight)
|
|
51
|
+
phase = "tool";
|
|
52
|
+
else if (last.event === "tool-end")
|
|
53
|
+
phase = "composing";
|
|
54
|
+
else
|
|
55
|
+
phase = "prompt";
|
|
56
|
+
const hookChannelHealthy = o.edges.length > 0;
|
|
57
|
+
// Hook-derived state. The pane fallback (dialog / permission-prompt) is fused
|
|
58
|
+
// in by the state/wait consolidation; here state reflects only the reliable
|
|
59
|
+
// hook signal: working unless the turn has ended (or no signal at all).
|
|
60
|
+
const state = phase === "done" ? "idle" : phase === "unknown" ? "unknown" : "working";
|
|
61
|
+
// `agentChannelHealthy` is a FUSED (pane-aware) judgment — `believe()` computes
|
|
62
|
+
// the real value; from hooks/transcript alone there is no drift evidence, so the
|
|
63
|
+
// base is `true`. (deriveProgress is hook-only; the canary needs the pane.)
|
|
64
|
+
return {
|
|
65
|
+
phase,
|
|
66
|
+
toolInFlight,
|
|
67
|
+
transcriptCount: o.transcriptCount,
|
|
68
|
+
hookChannelHealthy,
|
|
69
|
+
agentChannelHealthy: true,
|
|
70
|
+
state,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Edges from the **current** session lifecycle only: everything from the latest
|
|
75
|
+
* `session-start` onward. The rendezvous is keyed by conversation id and *reused
|
|
76
|
+
* across resume*, so a crashed prior life leaves an unclosed `prompt-submit` (and
|
|
77
|
+
* a stale `session-start`) in the same file. Computing the belief over those
|
|
78
|
+
* poisons it (the false-`working`-after-resume bug). Resetting at the last
|
|
79
|
+
* `session-start` boundary is the one fix for that whole family. Edges are sorted
|
|
80
|
+
* by time, so the last `session-start` in the array is the most recent; with no
|
|
81
|
+
* `session-start` at all (a bare progress sequence) everything is kept.
|
|
82
|
+
*/
|
|
83
|
+
export function currentLifeEdges(edges) {
|
|
84
|
+
let lastStart = -1;
|
|
85
|
+
for (let i = 0; i < edges.length; i++) {
|
|
86
|
+
if (edges[i]?.event === "session-start")
|
|
87
|
+
lastStart = i;
|
|
88
|
+
}
|
|
89
|
+
return lastStart < 0 ? edges : edges.slice(lastStart);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Fuse the reliable signals + the pre-classified pane into one {@link Belief}.
|
|
93
|
+
* `state` precedence: pane-only modals (dialog/permission) → hook lifecycle
|
|
94
|
+
* (when the channel is live and a turn has happened) → pane (hooks silent/off).
|
|
95
|
+
* The hook-lifecycle branch carries one pane cross-check for its blind spot: a
|
|
96
|
+
* denied/abandoned tool leaves a dangling `tool-start` (no `tool-end`), so a
|
|
97
|
+
* settled idle pane overrides a stuck hook `working`. Only {@link currentLifeEdges}
|
|
98
|
+
* feed the belief, so a resumed session is never judged by its prior life's edges.
|
|
99
|
+
*/
|
|
100
|
+
export function believe(o) {
|
|
101
|
+
const edges = currentLifeEdges(o.edges);
|
|
102
|
+
const prog = deriveProgress({ edges, transcriptCount: o.transcriptCount });
|
|
103
|
+
// `weInterrupted` is authoritative; else the human-interrupt heuristic: the
|
|
104
|
+
// pane shows "Interrupted" AND is the post-interrupt draft (`unknown`), not a
|
|
105
|
+
// working/idle box (a new turn, or a resume replaying old "Interrupted" text).
|
|
106
|
+
const interrupted = o.weInterrupted === true || (o.pane.interrupted && o.pane.state === "unknown");
|
|
107
|
+
let state;
|
|
108
|
+
if (o.pane.state === "dialog" || o.pane.state === "permission-prompt") {
|
|
109
|
+
state = o.pane.state; // only the pane sees modals — they win
|
|
110
|
+
}
|
|
111
|
+
else if (interrupted) {
|
|
112
|
+
state = "unknown"; // aborted: hook phase is stale, pane is a draft (not idle)
|
|
113
|
+
}
|
|
114
|
+
else if (prog.hookChannelHealthy && prog.phase !== "unknown") {
|
|
115
|
+
// The reliable hook lifecycle (working / idle) — with ONE pane cross-check
|
|
116
|
+
// for its blind spot. A tool the consumer DENIES (or claude abandons) fires
|
|
117
|
+
// `tool-start` but never `tool-end`, so the hook phase stays `tool` → working
|
|
118
|
+
// forever though the turn is over. When the hooks say working but the pane
|
|
119
|
+
// has settled to a clean idle box, the turn actually ended — trust the pane.
|
|
120
|
+
// A genuinely in-flight tool never renders the idle box (it shows the
|
|
121
|
+
// "esc to interrupt" spinner), so this only ever fires on the dangling-tool
|
|
122
|
+
// case; any transient idle frame is filtered by wait()'s idle stabilization.
|
|
123
|
+
state = prog.state === "working" && o.pane.state === "idle" ? "idle" : prog.state;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
state = o.pane.state; // hooks silent (off, or no turn yet) → trust the pane
|
|
127
|
+
}
|
|
128
|
+
const lastStop = [...edges].reverse().find((e) => e.event === "stop");
|
|
129
|
+
const lastEdge = edges[edges.length - 1];
|
|
130
|
+
// Drift canary: against a non-empty pane, at least ONE channel must extract
|
|
131
|
+
// signal — a recognized pane state, a known interrupt, hook edges, or a parsed
|
|
132
|
+
// message. All blind at once ⇒ the agent's output format likely drifted.
|
|
133
|
+
const recognizedSomething = o.pane.state !== "unknown" ||
|
|
134
|
+
interrupted ||
|
|
135
|
+
prog.hookChannelHealthy ||
|
|
136
|
+
prog.transcriptCount > 0;
|
|
137
|
+
const agentChannelHealthy = o.pane.nonEmpty !== true || recognizedSomething;
|
|
138
|
+
return {
|
|
139
|
+
...prog,
|
|
140
|
+
state,
|
|
141
|
+
interrupted,
|
|
142
|
+
agentChannelHealthy,
|
|
143
|
+
...(lastStop === undefined ? {} : { lastStopAt: lastStop.at }),
|
|
144
|
+
...(lastEdge === undefined ? {} : { lastActivityAt: lastEdge.at }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Read the hook rendezvous into ordered {@link HookEdge}s (chronological).
|
|
149
|
+
* Empty when hooks are off, the agent has no hook spec, or the file is absent
|
|
150
|
+
* — degrades, never throws. A FULL read: used by `bootSession` (a one-shot, not
|
|
151
|
+
* a hot path); the per-poll session path is the incremental
|
|
152
|
+
* {@link import('./session-observer.js').SessionObserver}.
|
|
153
|
+
*/
|
|
154
|
+
export function readHookEdges(o) {
|
|
155
|
+
const hooks = o.agent.hooks;
|
|
156
|
+
if (hooks === undefined)
|
|
157
|
+
return [];
|
|
158
|
+
const edges = [];
|
|
159
|
+
for (const line of readLines(o.rendezvousPath)) {
|
|
160
|
+
const edge = hooks.parseMarker(line);
|
|
161
|
+
if (edge !== null)
|
|
162
|
+
edges.push(edge);
|
|
163
|
+
}
|
|
164
|
+
edges.sort((a, b) => a.at - b.at);
|
|
165
|
+
return edges;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer.js","sourceRoot":"","sources":["../../src/observe/observer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AgentDef } from "../agents/types.js";
|
|
2
|
+
import type { Message, State } from "../types.js";
|
|
3
|
+
import { type Belief } from "./observer.js";
|
|
4
|
+
/**
|
|
5
|
+
* The per-session stateful read core — the single owner of "what's true" with
|
|
6
|
+
* **bounded** reads. It holds incremental {@link TailReader}s over the hook
|
|
7
|
+
* rendezvous and the transcript, so each `state()`/`progress()`/`wait()` poll
|
|
8
|
+
* parses only the bytes appended since the last one (O(delta), not O(file)) — the
|
|
9
|
+
* fix for a long-lived session re-parsing its whole transcript every 150ms (F39).
|
|
10
|
+
*
|
|
11
|
+
* It accumulates the parsed edges + messages + ancestry graph across polls, and
|
|
12
|
+
* resolves the transcript path from the hook's own report (free — it's already
|
|
13
|
+
* in the edges) before falling back to the agent's locate. One per handle; every
|
|
14
|
+
* method that reads session state defers to it. (Boot still does a one-shot full
|
|
15
|
+
* read — it isn't a hot path.)
|
|
16
|
+
*/
|
|
17
|
+
export declare class SessionObserver {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(o: {
|
|
20
|
+
agent: AgentDef;
|
|
21
|
+
rendezvousPath?: string;
|
|
22
|
+
agentSessionId?: string;
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* The one fused {@link Belief}, given the caller's pre-classified pane and the
|
|
26
|
+
* handle's authoritative interrupt flag. Refreshes both channels first.
|
|
27
|
+
*/
|
|
28
|
+
belief(pane: {
|
|
29
|
+
state: State;
|
|
30
|
+
interrupted: boolean;
|
|
31
|
+
nonEmpty?: boolean;
|
|
32
|
+
}, weInterrupted: boolean): Belief;
|
|
33
|
+
/**
|
|
34
|
+
* Is the transcript **addressable** — do we hold an `agentSessionId` to locate
|
|
35
|
+
* it by, or has a hook edge reported its path? This is addressability, NOT file
|
|
36
|
+
* existence: a fresh session with an id (transcript not flushed yet) is
|
|
37
|
+
* locatable and reads empty legitimately. `false` means we have NO handle on
|
|
38
|
+
* where the transcript lives (an adopt-miss, a non-claudemux session, or a fork
|
|
39
|
+
* before its first hook edge) — reads are blind, and the handle throws
|
|
40
|
+
* `TranscriptUnlocatable` rather than returning a deceptive empty.
|
|
41
|
+
*/
|
|
42
|
+
transcriptLocatable(): boolean;
|
|
43
|
+
/** The accumulated messages + ancestry graph (for `messagesSince`/`turnComplete`). */
|
|
44
|
+
thread(): {
|
|
45
|
+
messages: readonly Message[];
|
|
46
|
+
parentOf: Map<string, string | undefined>;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=session-observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-observer.d.ts","sourceRoot":"","sources":["../../src/observe/session-observer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,KAAK,MAAM,EAAW,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAe;;gBAYd,CAAC,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;IA0DpF;;;OAGG;IACH,MAAM,CACJ,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,EAChE,aAAa,EAAE,OAAO,GACrB,MAAM;IAWT;;;;;;;;OAQG;IACH,mBAAmB,IAAI,OAAO;IAM9B,sFAAsF;IACtF,MAAM,IAAI;QAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;KAAE;CAKtF"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { TailReader } from "./incremental.js";
|
|
2
|
+
import { believe } from "./observer.js";
|
|
3
|
+
/**
|
|
4
|
+
* The per-session stateful read core — the single owner of "what's true" with
|
|
5
|
+
* **bounded** reads. It holds incremental {@link TailReader}s over the hook
|
|
6
|
+
* rendezvous and the transcript, so each `state()`/`progress()`/`wait()` poll
|
|
7
|
+
* parses only the bytes appended since the last one (O(delta), not O(file)) — the
|
|
8
|
+
* fix for a long-lived session re-parsing its whole transcript every 150ms (F39).
|
|
9
|
+
*
|
|
10
|
+
* It accumulates the parsed edges + messages + ancestry graph across polls, and
|
|
11
|
+
* resolves the transcript path from the hook's own report (free — it's already
|
|
12
|
+
* in the edges) before falling back to the agent's locate. One per handle; every
|
|
13
|
+
* method that reads session state defers to it. (Boot still does a one-shot full
|
|
14
|
+
* read — it isn't a hot path.)
|
|
15
|
+
*/
|
|
16
|
+
export class SessionObserver {
|
|
17
|
+
#agent;
|
|
18
|
+
#rendezvousPath;
|
|
19
|
+
#agentSessionId;
|
|
20
|
+
#rvReader = new TailReader();
|
|
21
|
+
#txReader = new TailReader();
|
|
22
|
+
#edges = [];
|
|
23
|
+
#messages = [];
|
|
24
|
+
#parentOf = new Map();
|
|
25
|
+
#txPath; // memoized once resolved
|
|
26
|
+
constructor(o) {
|
|
27
|
+
this.#agent = o.agent;
|
|
28
|
+
this.#rendezvousPath = o.rendezvousPath;
|
|
29
|
+
this.#agentSessionId = o.agentSessionId;
|
|
30
|
+
}
|
|
31
|
+
/** Incrementally fold new rendezvous lines into the cached edges. */
|
|
32
|
+
#refreshEdges() {
|
|
33
|
+
const path = this.#rendezvousPath;
|
|
34
|
+
const hooks = this.#agent.hooks;
|
|
35
|
+
if (path === undefined || hooks === undefined)
|
|
36
|
+
return;
|
|
37
|
+
const { reset, lines } = this.#rvReader.poll(path);
|
|
38
|
+
if (reset)
|
|
39
|
+
this.#edges = [];
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const edge = hooks.parseMarker(line);
|
|
42
|
+
if (edge !== null)
|
|
43
|
+
this.#edges.push(edge);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The transcript path: the hook-reported one (from the edges we already hold —
|
|
48
|
+
* authoritative, no extra read) preferred over the agent's fragile locate.
|
|
49
|
+
* Memoized once non-undefined (it is stable for a session's lifetime).
|
|
50
|
+
*/
|
|
51
|
+
#resolveTxPath() {
|
|
52
|
+
if (this.#txPath !== undefined)
|
|
53
|
+
return this.#txPath;
|
|
54
|
+
for (let i = this.#edges.length - 1; i >= 0; i--) {
|
|
55
|
+
const p = this.#edges[i]?.transcriptPath;
|
|
56
|
+
if (p !== undefined) {
|
|
57
|
+
this.#txPath = p;
|
|
58
|
+
return p;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const transcript = this.#agent.transcript;
|
|
62
|
+
if (transcript !== undefined && this.#agentSessionId !== undefined) {
|
|
63
|
+
this.#txPath = transcript.locate({ agentSessionId: this.#agentSessionId }) ?? undefined;
|
|
64
|
+
}
|
|
65
|
+
return this.#txPath;
|
|
66
|
+
}
|
|
67
|
+
/** Incrementally fold new transcript lines into the cached messages + graph. */
|
|
68
|
+
#refreshTranscript() {
|
|
69
|
+
const transcript = this.#agent.transcript;
|
|
70
|
+
const path = this.#resolveTxPath();
|
|
71
|
+
if (path === undefined || transcript === undefined)
|
|
72
|
+
return;
|
|
73
|
+
const { reset, lines } = this.#txReader.poll(path);
|
|
74
|
+
if (reset) {
|
|
75
|
+
this.#messages = [];
|
|
76
|
+
this.#parentOf = new Map();
|
|
77
|
+
}
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const m = transcript.parseLine(line);
|
|
80
|
+
if (m !== null)
|
|
81
|
+
this.#messages.push(m);
|
|
82
|
+
const e = transcript.parseEdge?.(line);
|
|
83
|
+
if (e !== null && e !== undefined)
|
|
84
|
+
this.#parentOf.set(e.id, e.parentId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The one fused {@link Belief}, given the caller's pre-classified pane and the
|
|
89
|
+
* handle's authoritative interrupt flag. Refreshes both channels first.
|
|
90
|
+
*/
|
|
91
|
+
belief(pane, weInterrupted) {
|
|
92
|
+
this.#refreshEdges();
|
|
93
|
+
this.#refreshTranscript();
|
|
94
|
+
return believe({
|
|
95
|
+
edges: this.#edges,
|
|
96
|
+
transcriptCount: this.#messages.length,
|
|
97
|
+
pane,
|
|
98
|
+
weInterrupted,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Is the transcript **addressable** — do we hold an `agentSessionId` to locate
|
|
103
|
+
* it by, or has a hook edge reported its path? This is addressability, NOT file
|
|
104
|
+
* existence: a fresh session with an id (transcript not flushed yet) is
|
|
105
|
+
* locatable and reads empty legitimately. `false` means we have NO handle on
|
|
106
|
+
* where the transcript lives (an adopt-miss, a non-claudemux session, or a fork
|
|
107
|
+
* before its first hook edge) — reads are blind, and the handle throws
|
|
108
|
+
* `TranscriptUnlocatable` rather than returning a deceptive empty.
|
|
109
|
+
*/
|
|
110
|
+
transcriptLocatable() {
|
|
111
|
+
if (this.#agentSessionId !== undefined)
|
|
112
|
+
return true;
|
|
113
|
+
this.#refreshEdges();
|
|
114
|
+
return this.#edges.some((e) => e.transcriptPath !== undefined);
|
|
115
|
+
}
|
|
116
|
+
/** The accumulated messages + ancestry graph (for `messagesSince`/`turnComplete`). */
|
|
117
|
+
thread() {
|
|
118
|
+
this.#refreshEdges(); // so the hook transcript-path is preferred on a first read
|
|
119
|
+
this.#refreshTranscript();
|
|
120
|
+
return { messages: this.#messages, parentOf: this.#parentOf };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=session-observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-observer.js","sourceRoot":"","sources":["../../src/observe/session-observer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 adopt}. Mirrors {@link CreateOptions} minus the spawn-only
|
|
6
|
+
* fields (`cwd`, `extraArgs`, `env`, `bootTimeoutMs`, `trustWorkspace`) — adopt
|
|
7
|
+
* neither spawns nor boots.
|
|
8
|
+
*/
|
|
9
|
+
export interface AdoptOptions {
|
|
10
|
+
/** Name of the session to re-adopt; must match the live session's name. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Namespace prefix (default: `"claudemux"`). Must match the live session's namespace. */
|
|
13
|
+
namespace?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Agent definition controlling state/idle classification (default: claude).
|
|
16
|
+
* MUST be the same agent the original `create()` used — the classifier reads
|
|
17
|
+
* THIS agent's `rules`, not the session's. Passing the wrong agent silently
|
|
18
|
+
* misclassifies `state()`/`wait()`. See README §adopt.
|
|
19
|
+
*/
|
|
20
|
+
agent?: AgentDef;
|
|
21
|
+
/** Backend the live session runs in (default: the process-wide shared default — stable socket). */
|
|
22
|
+
backend?: Backend;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Re-adopt a session that is already live but was created by another process —
|
|
26
|
+
* the mirror of {@link create}. Pure attach: no spawn, no boot, no dialog dismissal.
|
|
27
|
+
*
|
|
28
|
+
* After a successful adopt the consumer MUST call `state()` before driving the
|
|
29
|
+
* pane (covers wedged / mid-dialog). See README §adopt for the A/B/C recovery
|
|
30
|
+
* taxonomy and the single-writer invariant.
|
|
31
|
+
*
|
|
32
|
+
* @throws `InvalidSessionName` if `name`/`namespace` contain reserved characters
|
|
33
|
+
* (thrown before the exists-check).
|
|
34
|
+
* @throws `SessionGone` if no such session exists — incl. the whole backend
|
|
35
|
+
* server being down, which `exists()` reports as absence.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { adopt, SessionGone } from "claudemux";
|
|
40
|
+
* try {
|
|
41
|
+
* const session = await adopt({ name: "job" });
|
|
42
|
+
* await session.state(); // ALWAYS call state() before driving the pane
|
|
43
|
+
* } catch (err) {
|
|
44
|
+
* if (err instanceof SessionGone) {
|
|
45
|
+
* // the pane is gone — continue the conversation in a fresh one:
|
|
46
|
+
* // await resume({ name: "job-2", cwd, agentSessionId });
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function adopt(opts: AdoptOptions): Promise<SessionHandle>;
|
|
52
|
+
//# sourceMappingURL=adopt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../../src/session/adopt.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;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,mGAAmG;IACnG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CA0BtE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SessionGone } from "../errors.js";
|
|
2
|
+
import { AGENT_SESSION_ID_META_KEY } from "./constants.js";
|
|
3
|
+
import { attachHandle } from "./handle.js";
|
|
4
|
+
import { formatSessionLabel } from "./ref.js";
|
|
5
|
+
import { resolveSessionContext } from "./resolve.js";
|
|
6
|
+
/**
|
|
7
|
+
* Re-adopt a session that is already live but was created by another process —
|
|
8
|
+
* the mirror of {@link create}. Pure attach: no spawn, no boot, no dialog dismissal.
|
|
9
|
+
*
|
|
10
|
+
* After a successful adopt the consumer MUST call `state()` before driving the
|
|
11
|
+
* pane (covers wedged / mid-dialog). See README §adopt for the A/B/C recovery
|
|
12
|
+
* taxonomy and the single-writer invariant.
|
|
13
|
+
*
|
|
14
|
+
* @throws `InvalidSessionName` if `name`/`namespace` contain reserved characters
|
|
15
|
+
* (thrown before the exists-check).
|
|
16
|
+
* @throws `SessionGone` if no such session exists — incl. the whole backend
|
|
17
|
+
* server being down, which `exists()` reports as absence.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { adopt, SessionGone } from "claudemux";
|
|
22
|
+
* try {
|
|
23
|
+
* const session = await adopt({ name: "job" });
|
|
24
|
+
* await session.state(); // ALWAYS call state() before driving the pane
|
|
25
|
+
* } catch (err) {
|
|
26
|
+
* if (err instanceof SessionGone) {
|
|
27
|
+
* // the pane is gone — continue the conversation in a fresh one:
|
|
28
|
+
* // await resume({ name: "job-2", cwd, agentSessionId });
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export async function adopt(opts) {
|
|
34
|
+
const { ref, agent, backend } = resolveSessionContext(opts);
|
|
35
|
+
// Mirror of create()'s exists-check, inverted. adopt REQUIRES the session to
|
|
36
|
+
// be present; absence (including whole-server-down, which exists() collapses
|
|
37
|
+
// to false) is SessionGone — the symmetric counterpart to SessionExists.
|
|
38
|
+
if (!(await backend.exists(ref))) {
|
|
39
|
+
throw new SessionGone(formatSessionLabel(ref));
|
|
40
|
+
}
|
|
41
|
+
// Best-effort: recover the agent's conversation id from the session-meta the
|
|
42
|
+
// creating process cached. `undefined` on a miss (older/non-claudemux session,
|
|
43
|
+
// a creator that never wrote it, or a store read failure) — adopt never
|
|
44
|
+
// fabricates an id, it tells the truth and lets the consumer fall back to its
|
|
45
|
+
// own store. getSessionMeta already collapses "unreadable" to `undefined`.
|
|
46
|
+
const agentSessionId = await backend.getSessionMeta(ref, AGENT_SESSION_ID_META_KEY);
|
|
47
|
+
// Pure attach — no spawn, no boot, no dialog dismissal. The consumer MUST call
|
|
48
|
+
// state() after adopt to learn where the live pane stands.
|
|
49
|
+
return attachHandle({
|
|
50
|
+
backend,
|
|
51
|
+
agent,
|
|
52
|
+
namespace: ref.namespace,
|
|
53
|
+
name: ref.name,
|
|
54
|
+
...(agentSessionId === undefined ? {} : { agentSessionId }),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=adopt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/session/adopt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAuBrD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAkB;IAC5C,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAE5D,6EAA6E;IAC7E,6EAA6E;IAC7E,yEAAyE;IACzE,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,WAAW,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,6EAA6E;IAC7E,+EAA+E;IAC/E,wEAAwE;IACxE,8EAA8E;IAC9E,2EAA2E;IAC3E,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;IAEpF,+EAA+E;IAC/E,2DAA2D;IAC3D,OAAO,YAAY,CAAC;QAClB,OAAO;QACP,KAAK;QACL,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC;KAC5D,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AgentDef } from "../agents/types.js";
|
|
2
|
+
import type { Backend, SessionRef } from "../backends/types.js";
|
|
3
|
+
/** Boot options threaded from `create`. */
|
|
4
|
+
export interface BootOptions {
|
|
5
|
+
/** Total boot budget (default 60s). */
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Opt in to auto-dismissing the agent's workspace-trust dialog. Default
|
|
9
|
+
* **false** — trusting a folder is an authority grant the substrate does
|
|
10
|
+
* not make for the caller. Without it, an untrusted-cwd trust dialog
|
|
11
|
+
* throws `WorkspaceUntrusted` before any keystroke is sent. See that
|
|
12
|
+
* error's TSDoc for the persistent/global-trust caveats.
|
|
13
|
+
*/
|
|
14
|
+
trustWorkspace?: boolean;
|
|
15
|
+
/** The cwd being booted into — carried on `WorkspaceUntrusted` for the caller. */
|
|
16
|
+
cwd?: string;
|
|
17
|
+
/**
|
|
18
|
+
* The caller-chosen `agentSessionId` for this spawn, if any. Carried onto
|
|
19
|
+
* {@link AgentExitedDuringBoot} when the agent exits before ready, so the
|
|
20
|
+
* (overwhelmingly likely) collision case stays actionable. Omitted for a
|
|
21
|
+
* minted id — a v4 mint collides with ~zero probability, so attributing a
|
|
22
|
+
* minted-id boot-death to "id in use" would mislead.
|
|
23
|
+
*/
|
|
24
|
+
agentSessionId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Path to the session's hook rendezvous file, when hooks were injected
|
|
27
|
+
* (default-on). When set, boot **gates** readiness on the agent's
|
|
28
|
+
* `SessionStart` hook edge appearing here: a ready-looking pane alone never
|
|
29
|
+
* declares ready until the edge fires. Verified against claude 2.1.162:
|
|
30
|
+
* `SessionStart` fires only *after* any boot dialog is dismissed, once input
|
|
31
|
+
* is interactive — so an edge can never signal ready while a dialog is up.
|
|
32
|
+
* After the edge, boot still waits for a *stable* `isReady` pane (the first
|
|
33
|
+
* send otherwise races the welcome/MCP render storm and is lost). Omitted
|
|
34
|
+
* under `create({ hooks: false })`, where the pane is the only ready signal.
|
|
35
|
+
*/
|
|
36
|
+
rendezvousPath?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Boot the session: dismiss any matching dialogs in order, then wait for ready.
|
|
40
|
+
*
|
|
41
|
+
* **Ready signal:** a hook *gate* plus a pane *settle*.
|
|
42
|
+
* 1. **`session-start` hook edge — the authoritative "started" gate.** With
|
|
43
|
+
* hooks on (the default, `opts.rendezvousPath` set), boot will not declare
|
|
44
|
+
* ready until this edge fires — a ready-*looking* pane is NOT trusted on
|
|
45
|
+
* its own (the founder's "hooks, not screen-scraping" north star). The edge
|
|
46
|
+
* lands only once input is interactive and post-dialog. With hooks off
|
|
47
|
+
* there is no edge, so the pane is the only signal.
|
|
48
|
+
* 2. **Stable ready box — the delivery-safety settle.** Even after "started,"
|
|
49
|
+
* a fresh REPL is still painting its welcome/MCP render, and the *first*
|
|
50
|
+
* send pasted into that render storm is silently lost (verified). So boot
|
|
51
|
+
* returns only once the ready box has held *stable*, guaranteeing the input
|
|
52
|
+
* is paintable. Dialogs are handled before either check each iteration.
|
|
53
|
+
*
|
|
54
|
+
* Throws on the documented failures.
|
|
55
|
+
*
|
|
56
|
+
* @throws `WorkspaceUntrusted` if the workspace-trust dialog fires and
|
|
57
|
+
* `trustWorkspace` was not set — thrown *before* any keystroke, so no
|
|
58
|
+
* persistent trust flag is written.
|
|
59
|
+
* @throws `LoginRequired` if the login-method dialog fires.
|
|
60
|
+
* @throws `DialogStuck` if a recognized dialog persists after its response.
|
|
61
|
+
* @throws `AgentExitedDuringBoot` if the agent process exits (its session is
|
|
62
|
+
* reaped) before becoming ready — most often an `agentSessionId` collision.
|
|
63
|
+
* @throws `ReplTimeout` if the total budget elapses before a stable ready.
|
|
64
|
+
*/
|
|
65
|
+
export declare function bootSession(backend: Backend, agent: AgentDef, ref: SessionRef, opts?: BootOptions): Promise<void>;
|
|
66
|
+
//# sourceMappingURL=boot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../src/session/boot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AA6ChE,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,UAAU,EACf,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CAmEf"}
|