@polderlabs/bizar-omp 0.7.6 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/dist/cli/agents-ink.d.ts +34 -0
- package/dist/cli/agents-ink.d.ts.map +1 -1
- package/dist/cli/agents-ink.js +120 -30
- package/dist/cli/agents-ink.js.map +1 -1
- package/dist/cli/agents.d.ts +1 -0
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +37 -13
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/doctor.d.ts +4 -4
- package/dist/core/types.d.ts +13 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +15 -1
- package/dist/core/types.js.map +1 -1
- package/dist/extension.d.ts +2 -0
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +161 -59
- package/dist/extension.js.map +1 -1
- package/dist/omp/compatibility.d.ts +5 -5
- package/dist/omp/compatibility.d.ts.map +1 -1
- package/dist/omp/compatibility.js +2 -2
- package/dist/omp/compatibility.js.map +1 -1
- package/dist/omp/daemon-service.d.ts.map +1 -1
- package/dist/omp/daemon-service.js +4 -1
- package/dist/omp/daemon-service.js.map +1 -1
- package/dist/omp/lifecycle.d.ts +1 -1
- package/dist/omp/lifecycle.d.ts.map +1 -1
- package/dist/omp/lifecycle.js +2 -1
- package/dist/omp/lifecycle.js.map +1 -1
- package/dist/omp/omb-sessions.d.ts +60 -3
- package/dist/omp/omb-sessions.d.ts.map +1 -1
- package/dist/omp/omb-sessions.js +183 -19
- package/dist/omp/omb-sessions.js.map +1 -1
- package/dist/omp/omb-settings.d.ts +17 -0
- package/dist/omp/omb-settings.d.ts.map +1 -1
- package/dist/omp/omb-settings.js +10 -0
- package/dist/omp/omb-settings.js.map +1 -1
- package/dist/omp/terminal-title.d.ts +70 -0
- package/dist/omp/terminal-title.d.ts.map +1 -0
- package/dist/omp/terminal-title.js +123 -0
- package/dist/omp/terminal-title.js.map +1 -0
- package/dist/omp/tmux.d.ts +58 -0
- package/dist/omp/tmux.d.ts.map +1 -1
- package/dist/omp/tmux.js +153 -7
- package/dist/omp/tmux.js.map +1 -1
- package/dist/storage/journal.d.ts +1 -1
- package/dist/storage/journal.d.ts.map +1 -1
- package/dist/storage/journal.js +2 -1
- package/dist/storage/journal.js.map +1 -1
- package/docs/compatibility/baseline.json +8 -7
- package/docs/compatibility/supported-surfaces.json +2 -1
- package/docs/releases/0.7.7.md +3 -0
- package/docs/releases/0.7.8.md +21 -0
- package/docs/releases/support-matrix.md +1 -1
- package/package.json +1 -1
- package/skills/bizar-omp/SKILL.md +24 -0
- package/skills/omp-native-development/SKILL.md +1 -1
- package/skills/omp-native-development/references/accuracy-and-versioning.md +1 -1
- package/skills/omp-native-development/references/source-manifest.json +3 -3
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal tab title for a Bizar-owned session.
|
|
3
|
+
*
|
|
4
|
+
* OMP renders the tab title from its own session name, which for an `omb` host
|
|
5
|
+
* is the bare binary name — every tab reads "omb" and nothing distinguishes a
|
|
6
|
+
* working session from an idle one. `setTitle()` is the supported way to own
|
|
7
|
+
* that title, but an extension override is rendered verbatim: OMP's own
|
|
8
|
+
* run-state spinner never rewrites it. So the spinner has to be driven here,
|
|
9
|
+
* from the same turn boundaries that drive the hub's state.
|
|
10
|
+
*
|
|
11
|
+
* Frames are braille dots, the style OMP itself uses, so a Bizar title and a
|
|
12
|
+
* plain OMP title animate identically.
|
|
13
|
+
*/
|
|
14
|
+
/** Braille spinner frames, matching OMP's own working-state animation. */
|
|
15
|
+
export declare const TITLE_SPINNER_FRAMES: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
16
|
+
/** How often the working title advances a frame. */
|
|
17
|
+
export declare const TITLE_SPINNER_INTERVAL_MS = 120;
|
|
18
|
+
export type TitleState = "idle" | "working";
|
|
19
|
+
/**
|
|
20
|
+
* One rendered title, without any escape sequence.
|
|
21
|
+
*
|
|
22
|
+
* Kept pure so the exact text is testable without a terminal.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatSessionTitle(input: {
|
|
25
|
+
label: string;
|
|
26
|
+
state: TitleState;
|
|
27
|
+
frame: number;
|
|
28
|
+
}): string;
|
|
29
|
+
/** Session identity sources a title label can be resolved from. */
|
|
30
|
+
export interface SessionTitleSources {
|
|
31
|
+
/** OMP's generated/renamed conversation title, once one exists. */
|
|
32
|
+
sessionName?: string | undefined;
|
|
33
|
+
/** The Bizar host name, which is a hashed directory id rather than a title. */
|
|
34
|
+
ombSessionName?: string | undefined;
|
|
35
|
+
/** The working directory, used to name the project before a title exists. */
|
|
36
|
+
cwd?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The readable label for one session's tab.
|
|
40
|
+
*
|
|
41
|
+
* The host name is deliberately not used as the label: `bizar-<project>-<hash>`
|
|
42
|
+
* identifies a host, not a conversation, and putting it in the tab is exactly
|
|
43
|
+
* the unreadable title this replaces. OMP's session name is its conversation
|
|
44
|
+
* title once generated; before that the project directory names the work.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveSessionTitleLabel(sources: SessionTitleSources): string;
|
|
47
|
+
export interface SessionTitleController {
|
|
48
|
+
/** Point the title at a label, e.g. after the conversation is renamed. */
|
|
49
|
+
setLabel(label: string | undefined): void;
|
|
50
|
+
/** Enter or leave the working state; the spinner runs only while working. */
|
|
51
|
+
setState(state: TitleState): void;
|
|
52
|
+
/** Stop animating and leave the title in its last idle form. */
|
|
53
|
+
dispose(): void;
|
|
54
|
+
}
|
|
55
|
+
export interface SessionTitleOptions {
|
|
56
|
+
/** Emits one title update. Returns whether the write was accepted. */
|
|
57
|
+
write: (title: string) => void;
|
|
58
|
+
label?: string | undefined;
|
|
59
|
+
intervalMs?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Drive a spinner-backed tab title.
|
|
63
|
+
*
|
|
64
|
+
* The timer is only armed while working: an idle session writes its title once
|
|
65
|
+
* and then stops, so a finished agent costs nothing. `write` is expected to be
|
|
66
|
+
* OMP's `ctx.ui.setTitle`, which is unavailable in non-interactive modes; the
|
|
67
|
+
* controller then simply does nothing rather than throwing on every frame.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createSessionTitleController(options: SessionTitleOptions): SessionTitleController;
|
|
70
|
+
//# sourceMappingURL=terminal-title.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-title.d.ts","sourceRoot":"","sources":["../../src/omp/terminal-title.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB,YAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAEhG,oDAAoD;AACpD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAM5C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAMrG;AAED,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAc7E;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1C,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC,gEAAgE;IAChE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,sBAAsB,CAsDjG"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal tab title for a Bizar-owned session.
|
|
3
|
+
*
|
|
4
|
+
* OMP renders the tab title from its own session name, which for an `omb` host
|
|
5
|
+
* is the bare binary name — every tab reads "omb" and nothing distinguishes a
|
|
6
|
+
* working session from an idle one. `setTitle()` is the supported way to own
|
|
7
|
+
* that title, but an extension override is rendered verbatim: OMP's own
|
|
8
|
+
* run-state spinner never rewrites it. So the spinner has to be driven here,
|
|
9
|
+
* from the same turn boundaries that drive the hub's state.
|
|
10
|
+
*
|
|
11
|
+
* Frames are braille dots, the style OMP itself uses, so a Bizar title and a
|
|
12
|
+
* plain OMP title animate identically.
|
|
13
|
+
*/
|
|
14
|
+
/** Braille spinner frames, matching OMP's own working-state animation. */
|
|
15
|
+
export const TITLE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
16
|
+
/** How often the working title advances a frame. */
|
|
17
|
+
export const TITLE_SPINNER_INTERVAL_MS = 120;
|
|
18
|
+
/** Separator between the spinner and the session label, mirroring OMP. */
|
|
19
|
+
const WORKING_SEPARATOR = ":";
|
|
20
|
+
const IDLE_SEPARATOR = ">";
|
|
21
|
+
/**
|
|
22
|
+
* One rendered title, without any escape sequence.
|
|
23
|
+
*
|
|
24
|
+
* Kept pure so the exact text is testable without a terminal.
|
|
25
|
+
*/
|
|
26
|
+
export function formatSessionTitle(input) {
|
|
27
|
+
const label = input.label.trim();
|
|
28
|
+
if (!label)
|
|
29
|
+
return "";
|
|
30
|
+
if (input.state !== "working")
|
|
31
|
+
return `${label} ${IDLE_SEPARATOR}`;
|
|
32
|
+
const frame = TITLE_SPINNER_FRAMES[((input.frame % TITLE_SPINNER_FRAMES.length) + TITLE_SPINNER_FRAMES.length) % TITLE_SPINNER_FRAMES.length];
|
|
33
|
+
return `${frame} ${WORKING_SEPARATOR} ${label}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The readable label for one session's tab.
|
|
37
|
+
*
|
|
38
|
+
* The host name is deliberately not used as the label: `bizar-<project>-<hash>`
|
|
39
|
+
* identifies a host, not a conversation, and putting it in the tab is exactly
|
|
40
|
+
* the unreadable title this replaces. OMP's session name is its conversation
|
|
41
|
+
* title once generated; before that the project directory names the work.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveSessionTitleLabel(sources) {
|
|
44
|
+
const title = sources.sessionName?.trim();
|
|
45
|
+
if (title && title !== "omb")
|
|
46
|
+
return title;
|
|
47
|
+
const cwd = sources.cwd?.trim();
|
|
48
|
+
if (cwd) {
|
|
49
|
+
const base = cwd.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
|
|
50
|
+
if (base)
|
|
51
|
+
return base;
|
|
52
|
+
}
|
|
53
|
+
// Last resort: strip the hash off the Bizar host name, which still names the
|
|
54
|
+
// project even when no directory or title is available. A bare "omb" is the
|
|
55
|
+
// unreadable title this whole path exists to replace, so it is never used.
|
|
56
|
+
const host = sources.ombSessionName?.trim();
|
|
57
|
+
if (host)
|
|
58
|
+
return host.replace(/^bizar-/, "").replace(/-[a-f0-9]{10}(?:-.*)?$/, "") || host;
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Drive a spinner-backed tab title.
|
|
63
|
+
*
|
|
64
|
+
* The timer is only armed while working: an idle session writes its title once
|
|
65
|
+
* and then stops, so a finished agent costs nothing. `write` is expected to be
|
|
66
|
+
* OMP's `ctx.ui.setTitle`, which is unavailable in non-interactive modes; the
|
|
67
|
+
* controller then simply does nothing rather than throwing on every frame.
|
|
68
|
+
*/
|
|
69
|
+
export function createSessionTitleController(options) {
|
|
70
|
+
const intervalMs = options.intervalMs ?? TITLE_SPINNER_INTERVAL_MS;
|
|
71
|
+
let label = options.label ?? "";
|
|
72
|
+
let state = "idle";
|
|
73
|
+
let frame = 0;
|
|
74
|
+
let timer;
|
|
75
|
+
const render = () => formatSessionTitle({ label, state, frame });
|
|
76
|
+
const stopTimer = () => {
|
|
77
|
+
if (timer)
|
|
78
|
+
clearInterval(timer);
|
|
79
|
+
timer = undefined;
|
|
80
|
+
};
|
|
81
|
+
const emit = () => {
|
|
82
|
+
const title = render();
|
|
83
|
+
// An empty title would release OMP's override and strand the tab on the
|
|
84
|
+
// bare brand, so a nameless session writes nothing at all.
|
|
85
|
+
if (!title)
|
|
86
|
+
return;
|
|
87
|
+
options.write(title);
|
|
88
|
+
};
|
|
89
|
+
const startTimer = () => {
|
|
90
|
+
if (timer)
|
|
91
|
+
return;
|
|
92
|
+
timer = setInterval(() => {
|
|
93
|
+
frame += 1;
|
|
94
|
+
emit();
|
|
95
|
+
}, intervalMs);
|
|
96
|
+
// A spinner must never be the reason the process stays alive.
|
|
97
|
+
timer.unref?.();
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
setLabel(next) {
|
|
101
|
+
label = next ?? "";
|
|
102
|
+
emit();
|
|
103
|
+
},
|
|
104
|
+
setState(next) {
|
|
105
|
+
if (state === next)
|
|
106
|
+
return;
|
|
107
|
+
state = next;
|
|
108
|
+
if (state === "working") {
|
|
109
|
+
frame = 0;
|
|
110
|
+
emit();
|
|
111
|
+
startTimer();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
stopTimer();
|
|
115
|
+
frame = 0;
|
|
116
|
+
emit();
|
|
117
|
+
},
|
|
118
|
+
dispose() {
|
|
119
|
+
stopTimer();
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=terminal-title.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-title.js","sourceRoot":"","sources":["../../src/omp/terminal-title.ts"],"names":[],"mappings":"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"}
|
package/dist/omp/tmux.d.ts
CHANGED
|
@@ -11,6 +11,42 @@ export declare function shouldLaunchOmbInTmux(options?: TmuxLaunchEnvironment):
|
|
|
11
11
|
* A session id creates an additional durable session in the same project.
|
|
12
12
|
*/
|
|
13
13
|
export declare function ombSessionName(cwd: string, sessionId?: string, profile?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Re-point a live host at the generation an operator explicitly switched to.
|
|
16
|
+
*
|
|
17
|
+
* `bindNativeSessionId` writes the generation immutably on purpose: an
|
|
18
|
+
* unattended dead-pane recovery must never be talked onto a different journal.
|
|
19
|
+
* This is the one sanctioned exception, and it differs in three ways. The
|
|
20
|
+
* caller is an explicit `/resume`, not a recovery. The new generation is
|
|
21
|
+
* supplied, never inferred from whatever journal is newest in a shared project
|
|
22
|
+
* directory. And the previous value is returned for reporting.
|
|
23
|
+
*
|
|
24
|
+
* The previous value is deliberately *not* meant to be written back. OMP has
|
|
25
|
+
* already switched by the time this runs, so restoring the old generation would
|
|
26
|
+
* make `assertNativeSessionBinding` pass against the conversation this host has
|
|
27
|
+
* left — a silent divergence. A caller that cannot finish the switch must
|
|
28
|
+
* instead call {@link clearOmbHostGeneration} or drop the journal manifest, so
|
|
29
|
+
* the respawn check refuses.
|
|
30
|
+
*/
|
|
31
|
+
export declare function rebindOmbHostGeneration(session: string, nativeId: string, cwd?: string, env?: NodeJS.ProcessEnv): Promise<{
|
|
32
|
+
ok: true;
|
|
33
|
+
previous?: string;
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
reason: string;
|
|
37
|
+
previous?: string;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Clear a host's generation binding so a later respawn is refused.
|
|
41
|
+
*
|
|
42
|
+
* Used when OMP has already switched conversations but the host's records
|
|
43
|
+
* could not be brought into agreement. Restoring the *previous* generation
|
|
44
|
+
* would be actively unsafe: the respawn check would then pass against the old
|
|
45
|
+
* conversation while the host writes the new one. An absent binding always
|
|
46
|
+
* fails that check, so the respawn stops instead of resuming the wrong
|
|
47
|
+
* journal, and the operator is told to restart the host.
|
|
48
|
+
*/
|
|
49
|
+
export declare function clearOmbHostGeneration(session: string, cwd?: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
14
50
|
/** Candidate command names in lookup order, kept pure for cross-platform tests. */
|
|
15
51
|
export declare function executableCandidates(bin: string, platform: "win32" | "posix", pathext?: string): string[];
|
|
16
52
|
/** Resolve an executable on `$PATH`, or an explicit path when it contains a slash. */
|
|
@@ -99,4 +135,26 @@ export declare function createOmbSession(cwd: string, args: string[], env?: Node
|
|
|
99
135
|
name: string;
|
|
100
136
|
code: number;
|
|
101
137
|
}>;
|
|
138
|
+
/**
|
|
139
|
+
* Recreate a host for a stopped session, bound to the journal it already wrote.
|
|
140
|
+
*
|
|
141
|
+
* This is the only route back into a stopped conversation: attaching is
|
|
142
|
+
* impossible while nothing hosts it. `nativeId` must be the exact generation
|
|
143
|
+
* the selected row reported — a project directory holds every session for
|
|
144
|
+
* that project, so resolving "the newest journal" here would resume a
|
|
145
|
+
* different conversation than the one the operator chose. A row without a
|
|
146
|
+
* known generation is refused rather than guessed at.
|
|
147
|
+
*
|
|
148
|
+
* A session whose journal still sits in a Bizar private directory is migrated
|
|
149
|
+
* into OMP's project directory first: that is the directory the new host is
|
|
150
|
+
* started with, and `--resume` can only find a journal there. The migration is
|
|
151
|
+
* verified against the requested id, so a host is never started on a journal
|
|
152
|
+
* belonging to another session. The binding invariant is untouched — the fresh
|
|
153
|
+
* host records the same generation it always did, and a mismatched one still
|
|
154
|
+
* refuses.
|
|
155
|
+
*/
|
|
156
|
+
export declare function continueOmbSession(session: string, cwd: string, nativeId: string | undefined, env?: NodeJS.ProcessEnv): Promise<{
|
|
157
|
+
name: string;
|
|
158
|
+
code: number;
|
|
159
|
+
}>;
|
|
102
160
|
//# sourceMappingURL=tmux.d.ts.map
|
package/dist/omp/tmux.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../../src/omp/tmux.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAmBD,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAOlF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAaxF;
|
|
1
|
+
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../../src/omp/tmux.ts"],"names":[],"mappings":"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"}
|
package/dist/omp/tmux.js
CHANGED
|
@@ -99,8 +99,15 @@ function quiesceStdinForHandoff() {
|
|
|
99
99
|
}
|
|
100
100
|
const NATIVE_ID_OPTION = "@bizar-native-id";
|
|
101
101
|
const NATIVE_OWNER_OPTION = "@bizar-session-owner";
|
|
102
|
-
/**
|
|
103
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Read the native session header id from the exact directory a pane owns.
|
|
104
|
+
*
|
|
105
|
+
* `only` narrows the answer to one generation. A project directory holds every
|
|
106
|
+
* session for that project, so the newest journal is not necessarily the one an
|
|
107
|
+
* operator selected; a caller that must resume a specific conversation passes
|
|
108
|
+
* its id and gets `undefined` unless that exact journal is present.
|
|
109
|
+
*/
|
|
110
|
+
async function nativeSessionId(sessionDir, only) {
|
|
104
111
|
const journals = await listSessionJournals(sessionDir);
|
|
105
112
|
for (const journal of journals.toReversed()) {
|
|
106
113
|
const head = await readJournalHead(journal);
|
|
@@ -109,8 +116,11 @@ async function nativeSessionId(sessionDir) {
|
|
|
109
116
|
for (const line of head.split("\n")) {
|
|
110
117
|
try {
|
|
111
118
|
const entry = JSON.parse(line);
|
|
112
|
-
if (entry.type
|
|
113
|
-
|
|
119
|
+
if (entry.type !== "session" || typeof entry.id !== "string" || !entry.id.trim())
|
|
120
|
+
continue;
|
|
121
|
+
const id = entry.id.trim();
|
|
122
|
+
if (!only || id === only || id.startsWith(only))
|
|
123
|
+
return id;
|
|
114
124
|
}
|
|
115
125
|
catch { /* a partial journal line is not a native session header */ }
|
|
116
126
|
}
|
|
@@ -121,21 +131,31 @@ async function nativeSessionId(sessionDir) {
|
|
|
121
131
|
* Bind a tmux session to the native journal generation it started. Once set,
|
|
122
132
|
* the option is never replaced: a conflicting journal is an unsafe recovery,
|
|
123
133
|
* not an invitation to repoint the tmux host.
|
|
134
|
+
*
|
|
135
|
+
* `expectedId` pins the generation for a host that is being restarted onto a
|
|
136
|
+
* specific conversation. Without it the id would be resolved from whatever
|
|
137
|
+
* journal happens to be newest in the directory, which in a shared project
|
|
138
|
+
* bucket is a different session than the operator chose.
|
|
124
139
|
*/
|
|
125
|
-
async function bindNativeSessionId(tmux, session, sessionDir, cwd, childEnv, existingSession = false) {
|
|
140
|
+
async function bindNativeSessionId(tmux, session, sessionDir, cwd, childEnv, existingSession = false, expectedId) {
|
|
126
141
|
const legacyPrivateDir = resolve(sessionDir) === resolve(ombSessionDir(session, childEnv));
|
|
127
|
-
let nativeId =
|
|
142
|
+
let nativeId = expectedId
|
|
143
|
+
?? ombNativeSessionReference(session, childEnv)?.id
|
|
144
|
+
?? (legacyPrivateDir ? await nativeSessionId(sessionDir) : undefined);
|
|
128
145
|
if (!nativeId && existingSession) {
|
|
129
146
|
const pane = await readTmux(tmux, ["list-panes", "-t", `${session}:0.0`, "-F", "#{pane_start_command}"], cwd, childEnv);
|
|
130
147
|
const paneDir = pane.code === 0 ? sessionDirFromPaneCommand(pane.output.trim()) : undefined;
|
|
131
148
|
if (paneDir && (resolve(paneDir) === resolve(ombSessionDir(session, childEnv)) || (await listSessionJournals(paneDir)).length === 1)) {
|
|
132
|
-
nativeId = await nativeSessionId(paneDir);
|
|
149
|
+
nativeId = await nativeSessionId(paneDir, expectedId);
|
|
133
150
|
}
|
|
134
151
|
}
|
|
135
152
|
// A newly started OMP can still be creating its first journal. Its next
|
|
136
153
|
// launch records the binding; a dead pane without a binding is refused.
|
|
137
154
|
if (!nativeId)
|
|
138
155
|
return 0;
|
|
156
|
+
if (expectedId && !expectedId.startsWith(nativeId) && !nativeId.startsWith(expectedId)) {
|
|
157
|
+
throw new Error(`Bizar tmux session ${session} would bind native generation ${nativeId} instead of the requested ${expectedId}`);
|
|
158
|
+
}
|
|
139
159
|
const stored = await readTmux(tmux, ["show-options", "-t", session, "-v", NATIVE_ID_OPTION], cwd, childEnv);
|
|
140
160
|
if (stored.code === 0 && stored.output.trim()) {
|
|
141
161
|
if (stored.output.trim() !== nativeId) {
|
|
@@ -161,6 +181,71 @@ async function assertNativeSessionBinding(tmux, session, sessionDir, cwd, childE
|
|
|
161
181
|
throw new Error(`Refusing to respawn ${session}: its tmux native session generation does not match ${sessionDir}`);
|
|
162
182
|
}
|
|
163
183
|
}
|
|
184
|
+
/** The generation a host is currently bound to, reported to the operator. */
|
|
185
|
+
async function readNativeSessionBinding(tmux, session, cwd, childEnv) {
|
|
186
|
+
const stored = await readTmux(tmux, ["show-options", "-t", session, "-v", NATIVE_ID_OPTION], cwd, childEnv);
|
|
187
|
+
const value = stored.output.trim();
|
|
188
|
+
return stored.code === 0 && value ? value : undefined;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Re-point a live host at the generation an operator explicitly switched to.
|
|
192
|
+
*
|
|
193
|
+
* `bindNativeSessionId` writes the generation immutably on purpose: an
|
|
194
|
+
* unattended dead-pane recovery must never be talked onto a different journal.
|
|
195
|
+
* This is the one sanctioned exception, and it differs in three ways. The
|
|
196
|
+
* caller is an explicit `/resume`, not a recovery. The new generation is
|
|
197
|
+
* supplied, never inferred from whatever journal is newest in a shared project
|
|
198
|
+
* directory. And the previous value is returned for reporting.
|
|
199
|
+
*
|
|
200
|
+
* The previous value is deliberately *not* meant to be written back. OMP has
|
|
201
|
+
* already switched by the time this runs, so restoring the old generation would
|
|
202
|
+
* make `assertNativeSessionBinding` pass against the conversation this host has
|
|
203
|
+
* left — a silent divergence. A caller that cannot finish the switch must
|
|
204
|
+
* instead call {@link clearOmbHostGeneration} or drop the journal manifest, so
|
|
205
|
+
* the respawn check refuses.
|
|
206
|
+
*/
|
|
207
|
+
export async function rebindOmbHostGeneration(session, nativeId, cwd = process.cwd(), env = process.env) {
|
|
208
|
+
if (!isOmbSessionName(session))
|
|
209
|
+
return { ok: false, reason: "not a Bizar session name" };
|
|
210
|
+
const id = nativeId.trim();
|
|
211
|
+
if (!id)
|
|
212
|
+
return { ok: false, reason: "no native session generation was supplied" };
|
|
213
|
+
const tmux = resolveMultiplexerBin(env);
|
|
214
|
+
const childEnv = { ...env, BIZAR_OMP_NO_TMUX: "1" };
|
|
215
|
+
const previous = await readNativeSessionBinding(tmux, session, cwd, childEnv);
|
|
216
|
+
if (previous === id)
|
|
217
|
+
return { ok: true, previous };
|
|
218
|
+
// No `-o`: this replaces an immutable value on purpose, and only here.
|
|
219
|
+
const set = await runTmux(tmux, ["set-option", "-t", session, NATIVE_ID_OPTION, id], cwd, childEnv);
|
|
220
|
+
if (set !== 0)
|
|
221
|
+
return { ok: false, reason: `multiplexer refused the rebind (exit ${set})`, previous };
|
|
222
|
+
const confirmed = await readNativeSessionBinding(tmux, session, cwd, childEnv);
|
|
223
|
+
if (confirmed !== id)
|
|
224
|
+
return { ok: false, reason: "the rebind did not take effect", previous };
|
|
225
|
+
return { ok: true, previous };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Clear a host's generation binding so a later respawn is refused.
|
|
229
|
+
*
|
|
230
|
+
* Used when OMP has already switched conversations but the host's records
|
|
231
|
+
* could not be brought into agreement. Restoring the *previous* generation
|
|
232
|
+
* would be actively unsafe: the respawn check would then pass against the old
|
|
233
|
+
* conversation while the host writes the new one. An absent binding always
|
|
234
|
+
* fails that check, so the respawn stops instead of resuming the wrong
|
|
235
|
+
* journal, and the operator is told to restart the host.
|
|
236
|
+
*/
|
|
237
|
+
export async function clearOmbHostGeneration(session, cwd = process.cwd(), env = process.env) {
|
|
238
|
+
if (!isOmbSessionName(session))
|
|
239
|
+
return false;
|
|
240
|
+
const tmux = resolveMultiplexerBin(env);
|
|
241
|
+
const childEnv = { ...env, BIZAR_OMP_NO_TMUX: "1" };
|
|
242
|
+
const cleared = await runTmux(tmux, ["set-option", "-u", "-t", session, NATIVE_ID_OPTION], cwd, childEnv);
|
|
243
|
+
if (cleared !== 0)
|
|
244
|
+
return false;
|
|
245
|
+
// Confirm the invalidation landed: a binding that survived would let the
|
|
246
|
+
// respawn check pass against a journal this host no longer writes.
|
|
247
|
+
return (await readNativeSessionBinding(tmux, session, cwd, childEnv)) === undefined;
|
|
248
|
+
}
|
|
164
249
|
async function bindNativeSessionOwner(tmux, session, cwd, childEnv, owner = session) {
|
|
165
250
|
const stored = await readTmux(tmux, ["show-options", "-t", session, "-v", NATIVE_OWNER_OPTION], cwd, childEnv);
|
|
166
251
|
if (stored.code === 0 && stored.output.trim())
|
|
@@ -712,4 +797,65 @@ export async function createOmbSession(cwd, args, env = process.env, sessionId)
|
|
|
712
797
|
clearOmbSessionDeleted(session, env);
|
|
713
798
|
return { name: session, code: preparedCode };
|
|
714
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Recreate a host for a stopped session, bound to the journal it already wrote.
|
|
802
|
+
*
|
|
803
|
+
* This is the only route back into a stopped conversation: attaching is
|
|
804
|
+
* impossible while nothing hosts it. `nativeId` must be the exact generation
|
|
805
|
+
* the selected row reported — a project directory holds every session for
|
|
806
|
+
* that project, so resolving "the newest journal" here would resume a
|
|
807
|
+
* different conversation than the one the operator chose. A row without a
|
|
808
|
+
* known generation is refused rather than guessed at.
|
|
809
|
+
*
|
|
810
|
+
* A session whose journal still sits in a Bizar private directory is migrated
|
|
811
|
+
* into OMP's project directory first: that is the directory the new host is
|
|
812
|
+
* started with, and `--resume` can only find a journal there. The migration is
|
|
813
|
+
* verified against the requested id, so a host is never started on a journal
|
|
814
|
+
* belonging to another session. The binding invariant is untouched — the fresh
|
|
815
|
+
* host records the same generation it always did, and a mismatched one still
|
|
816
|
+
* refuses.
|
|
817
|
+
*/
|
|
818
|
+
export async function continueOmbSession(session, cwd, nativeId, env = process.env) {
|
|
819
|
+
if (!isOmbSessionName(session))
|
|
820
|
+
return { name: session, code: 2 };
|
|
821
|
+
const id = nativeId?.trim();
|
|
822
|
+
if (!id)
|
|
823
|
+
return { name: session, code: 3 };
|
|
824
|
+
const tmux = resolveMultiplexerBin(env);
|
|
825
|
+
const omp = env.BIZAR_OMP_BIN || "omp";
|
|
826
|
+
const settings = loadOmbSettings(env);
|
|
827
|
+
const childEnv = { ...env, BIZAR_OMP_NO_TMUX: "1" };
|
|
828
|
+
const journalDir = defaultOmpSessionDir(cwd, env);
|
|
829
|
+
// The journal must be reachable from the directory the host will search.
|
|
830
|
+
// A legacy private journal is handed to OMP's own picker directory first.
|
|
831
|
+
let resolvable = await nativeSessionId(journalDir, id);
|
|
832
|
+
if (!resolvable && isLegacyOmbSessionDir(ombSessionDir(session, env), session, env)) {
|
|
833
|
+
const migrated = await migrateLegacyOmbSessionJournal(session, ombSessionDir(session, env), cwd, env);
|
|
834
|
+
if (!migrated.ok)
|
|
835
|
+
return { name: session, code: 6 };
|
|
836
|
+
if (migrated.id !== id)
|
|
837
|
+
return { name: session, code: 7 };
|
|
838
|
+
resolvable = await nativeSessionId(journalDir, id);
|
|
839
|
+
}
|
|
840
|
+
// Fail closed unless the journal OMP would resume is really in the directory
|
|
841
|
+
// the host will look in; otherwise `--resume` silently starts a new session.
|
|
842
|
+
if (!resolvable)
|
|
843
|
+
return { name: session, code: 5 };
|
|
844
|
+
const hasSession = await runTmux(tmux, ["has-session", "-t", session], cwd, childEnv);
|
|
845
|
+
if (hasSession === 0)
|
|
846
|
+
return { name: session, code: 4 };
|
|
847
|
+
const created = await runTmux(tmux, ["new-session", "-d", "-s", session, "-e", `BIZAR_OMP_SESSION_NAME=${session}`, "-c", cwd, omp, "--session-dir", journalDir, "--resume", id], cwd, childEnv);
|
|
848
|
+
if (created !== 0)
|
|
849
|
+
return { name: session, code: created };
|
|
850
|
+
const ownerBound = await bindNativeSessionOwner(tmux, session, cwd, childEnv);
|
|
851
|
+
if (ownerBound !== 0)
|
|
852
|
+
return { name: session, code: ownerBound };
|
|
853
|
+
const bound = await bindNativeSessionId(tmux, session, journalDir, cwd, childEnv, false, id);
|
|
854
|
+
if (bound !== 0)
|
|
855
|
+
return { name: session, code: bound };
|
|
856
|
+
const prepared = await prepareOmbSession(tmux, omp, session, cwd, childEnv, settings.mouseScrolling);
|
|
857
|
+
if (prepared === 0)
|
|
858
|
+
clearOmbSessionDeleted(session, env);
|
|
859
|
+
return { name: session, code: prepared };
|
|
860
|
+
}
|
|
715
861
|
//# sourceMappingURL=tmux.js.map
|