@polderlabs/bizar-omp 0.4.7 → 0.4.9
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 +52 -3
- package/dist/cli/agents-ink.d.ts.map +1 -1
- package/dist/cli/agents-ink.js +73 -37
- package/dist/cli/agents-ink.js.map +1 -1
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +15 -13
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/directory-selector.d.ts.map +1 -1
- package/dist/cli/directory-selector.js +3 -1
- package/dist/cli/directory-selector.js.map +1 -1
- package/dist/cli/doctor.d.ts +6 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +17 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/main.js +0 -0
- package/dist/cli/omb-daemon.d.ts +2 -0
- package/dist/cli/omb-daemon.d.ts.map +1 -0
- package/dist/cli/omb-daemon.js +113 -0
- package/dist/cli/omb-daemon.js.map +1 -0
- package/dist/cli/omb-dashboard.d.ts +12 -0
- package/dist/cli/omb-dashboard.d.ts.map +1 -0
- package/dist/cli/omb-dashboard.js +103 -0
- package/dist/cli/omb-dashboard.js.map +1 -0
- package/dist/cli/omb.js +8 -0
- package/dist/cli/omb.js.map +1 -1
- package/dist/cli/omp-theme.d.ts +2 -2
- package/dist/cli/omp-theme.d.ts.map +1 -1
- package/dist/cli/omp-theme.js +4 -2
- package/dist/cli/omp-theme.js.map +1 -1
- package/dist/cli/process.d.ts.map +1 -1
- package/dist/cli/process.js +5 -1
- package/dist/cli/process.js.map +1 -1
- package/dist/cli/setup-ui.js +1 -1
- package/dist/cli/setup-ui.js.map +1 -1
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +51 -0
- package/dist/extension.js.map +1 -1
- package/dist/omp/daemon-service.d.ts +24 -0
- package/dist/omp/daemon-service.d.ts.map +1 -0
- package/dist/omp/daemon-service.js +272 -0
- package/dist/omp/daemon-service.js.map +1 -0
- package/dist/omp/daemon.d.ts +48 -0
- package/dist/omp/daemon.d.ts.map +1 -0
- package/dist/omp/daemon.js +193 -0
- package/dist/omp/daemon.js.map +1 -0
- package/dist/omp/dashboard-html.d.ts +9 -0
- package/dist/omp/dashboard-html.d.ts.map +1 -0
- package/dist/omp/dashboard-html.js +594 -0
- package/dist/omp/dashboard-html.js.map +1 -0
- package/dist/omp/model-role-assignment.d.ts +3 -0
- package/dist/omp/model-role-assignment.d.ts.map +1 -1
- package/dist/omp/model-role-assignment.js +28 -6
- package/dist/omp/model-role-assignment.js.map +1 -1
- package/dist/omp/omb-sessions.d.ts +47 -2
- package/dist/omp/omb-sessions.d.ts.map +1 -1
- package/dist/omp/omb-sessions.js +236 -108
- package/dist/omp/omb-sessions.js.map +1 -1
- package/dist/omp/session-activity.d.ts +71 -0
- package/dist/omp/session-activity.d.ts.map +1 -0
- package/dist/omp/session-activity.js +361 -0
- package/dist/omp/session-activity.js.map +1 -0
- package/dist/omp/tmux.d.ts +24 -0
- package/dist/omp/tmux.d.ts.map +1 -1
- package/dist/omp/tmux.js +168 -49
- package/dist/omp/tmux.js.map +1 -1
- package/docs/compatibility/baseline.json +1 -1
- package/docs/releases/0.4.8.md +5 -0
- package/docs/releases/0.4.9.md +6 -0
- package/package.json +1 -1
|
@@ -38,11 +38,11 @@ export const BIZAR_ROLE_PROFILES = [
|
|
|
38
38
|
{ role: "bizar_research", purpose: "evidence-grounded investigation", preference: "balanced", minContextWindow: 128_000, requiresReasoning: true },
|
|
39
39
|
{ role: "bizar_plan", purpose: "dependencies and acceptance planning", preference: "strong", minContextWindow: 128_000, requiresReasoning: true },
|
|
40
40
|
{ role: "bizar_architect", purpose: "cross-component design and tradeoffs", preference: "strong", minContextWindow: 128_000, requiresReasoning: true },
|
|
41
|
-
{ role: "bizar_build", purpose: "focused implementation", preference: "balanced", minContextWindow: 64_000, requiresReasoning: true },
|
|
41
|
+
{ role: "bizar_build", purpose: "focused implementation", preference: "balanced", minContextWindow: 64_000, requiresReasoning: true, providerPreference: "local" },
|
|
42
42
|
{ role: "bizar_review", purpose: "independent correctness review", preference: "strong", minContextWindow: 128_000, requiresReasoning: true, preferDistinctFrom: "bizar_build" },
|
|
43
43
|
{ role: "bizar_security", purpose: "security and trust-boundary review", preference: "strong", minContextWindow: 128_000, requiresReasoning: true, preferDistinctFrom: "bizar_build" },
|
|
44
|
-
{ role: "bizar_verify", purpose: "reproducible checks and evidence", preference: "fast", minContextWindow: 32_000, requiresReasoning: true },
|
|
45
|
-
{ role: "bizar_docs", purpose: "documentation and release alignment", preference: "fast", minContextWindow: 32_000, requiresReasoning: false },
|
|
44
|
+
{ role: "bizar_verify", purpose: "reproducible checks and evidence", preference: "fast", minContextWindow: 32_000, requiresReasoning: true, providerPreference: "local" },
|
|
45
|
+
{ role: "bizar_docs", purpose: "documentation and release alignment", preference: "fast", minContextWindow: 32_000, requiresReasoning: false, providerPreference: "local" },
|
|
46
46
|
];
|
|
47
47
|
/**
|
|
48
48
|
* Kept as a small compatibility fallback for older OMP hosts that do not expose
|
|
@@ -78,17 +78,39 @@ const maxThinkingRank = (model) => {
|
|
|
78
78
|
return Math.max(0, ...thinkingLevels(model.thinking).map(level => THINKING_LEVELS.indexOf(level)));
|
|
79
79
|
};
|
|
80
80
|
const hourlyCost = (model) => (model.cost?.input ?? 0) + (model.cost?.output ?? 0);
|
|
81
|
+
/**
|
|
82
|
+
* Providers that serve models from the local machine. Used to bias role
|
|
83
|
+
* assignment: locally hosted models cost nothing per token and keep work
|
|
84
|
+
* on-device, but they must never disqualify a capable cloud fallback.
|
|
85
|
+
*/
|
|
86
|
+
const LOCAL_PROVIDERS = {
|
|
87
|
+
"llama.cpp": true,
|
|
88
|
+
llamacpp: true,
|
|
89
|
+
ollama: true,
|
|
90
|
+
lmstudio: true,
|
|
91
|
+
"lm-studio": true,
|
|
92
|
+
vllm: true,
|
|
93
|
+
jan: true,
|
|
94
|
+
gpt4all: true,
|
|
95
|
+
};
|
|
96
|
+
export function isLocalModel(model) {
|
|
97
|
+
return LOCAL_PROVIDERS[model.provider.toLowerCase()] === true;
|
|
98
|
+
}
|
|
81
99
|
function qualityScore(model, profile) {
|
|
82
100
|
const context = Math.min(2, Math.log10(Math.max(1, model.contextWindow ?? 0)) / 6);
|
|
83
101
|
const output = Math.min(1, Math.log10(Math.max(1, model.maxTokens ?? 0)) / 6);
|
|
84
102
|
const reasoning = model.reasoning ? 1 : 0;
|
|
85
103
|
const thinking = maxThinkingRank(model) / 5;
|
|
86
104
|
const costPenalty = Math.min(1, Math.log10(1 + hourlyCost(model)) / 3);
|
|
105
|
+
// A role may prefer locally hosted models (free per token, on-device).
|
|
106
|
+
// The bonus is additive and opt-in per profile: a local model that fits
|
|
107
|
+
// overtakes comparable cloud models, while a mismatch still falls back.
|
|
108
|
+
const providerBias = profile.providerPreference === "local" && isLocalModel(model) ? 0.15 : 0;
|
|
87
109
|
if (profile.preference === "fast")
|
|
88
|
-
return context * 0.2 + output * 0.1 + reasoning * 0.2 + thinking * 0.1 - costPenalty * 0.4;
|
|
110
|
+
return providerBias + context * 0.2 + output * 0.1 + reasoning * 0.2 + thinking * 0.1 - costPenalty * 0.4;
|
|
89
111
|
if (profile.preference === "strong")
|
|
90
|
-
return context * 0.25 + output * 0.15 + reasoning * 0.3 + thinking * 0.3 - costPenalty * 0.05;
|
|
91
|
-
return context * 0.25 + output * 0.15 + reasoning * 0.25 + thinking * 0.2 - costPenalty * 0.2;
|
|
112
|
+
return providerBias + context * 0.25 + output * 0.15 + reasoning * 0.3 + thinking * 0.3 - costPenalty * 0.05;
|
|
113
|
+
return providerBias + context * 0.25 + output * 0.15 + reasoning * 0.25 + thinking * 0.2 - costPenalty * 0.2;
|
|
92
114
|
}
|
|
93
115
|
function familyOf(query, model) {
|
|
94
116
|
return query.family?.(model) ?? model.provider.toLowerCase();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-role-assignment.js","sourceRoot":"","sources":["../../src/omp/model-role-assignment.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model-role-assignment.js","sourceRoot":"","sources":["../../src/omp/model-role-assignment.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,29 @@
|
|
|
1
|
+
import { type OmbSessionActivity } from "./session-activity.js";
|
|
1
2
|
export declare const BIZAR_SESSION_PREFIX = "bizar-";
|
|
2
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Connection and activity state of one Bizar-owned session.
|
|
5
|
+
*
|
|
6
|
+
* `working` and `waiting` describe what the agent is doing, `connected` and
|
|
7
|
+
* `disconnected` describe whether an interactive client is attached to an idle
|
|
8
|
+
* session, and `stopped` means no host is observed and the journal is silent.
|
|
9
|
+
*/
|
|
10
|
+
export type OmbSessionState = "working" | "waiting" | "connected" | "disconnected" | "stopped";
|
|
3
11
|
export interface OmbSessionInfo {
|
|
4
12
|
name: string;
|
|
5
13
|
cwd: string;
|
|
6
14
|
project: string;
|
|
7
15
|
id: string;
|
|
8
16
|
state: OmbSessionState;
|
|
17
|
+
/** Privacy-safe live activity, when the session journal exposes it. */
|
|
18
|
+
activity?: OmbSessionActivity;
|
|
19
|
+
/**
|
|
20
|
+
* True when tmux currently hosts this session.
|
|
21
|
+
*
|
|
22
|
+
* A live OMP process whose host `omb` never created, or whose host died
|
|
23
|
+
* while the agent kept working, has no attachable tmux session. Absent for
|
|
24
|
+
* synthetic records, which are treated as hosted.
|
|
25
|
+
*/
|
|
26
|
+
hosted?: boolean;
|
|
9
27
|
command: string;
|
|
10
28
|
pid: string;
|
|
11
29
|
/** Native OMP session id, when its journal is available. */
|
|
@@ -19,12 +37,39 @@ export interface OmbSessionInfo {
|
|
|
19
37
|
createdAt?: number;
|
|
20
38
|
lastAttachedAt?: number;
|
|
21
39
|
}
|
|
40
|
+
export interface ListOmbSessionsOptions {
|
|
41
|
+
/** Include Bizar sessions whose tmux host exited and whose journal is silent. Defaults to false. */
|
|
42
|
+
includeStopped?: boolean;
|
|
43
|
+
}
|
|
22
44
|
/** Keep native OMP journals separate so each durable `omb` host has one exact session. */
|
|
23
45
|
export declare function ombSessionDir(session: string, env?: NodeJS.ProcessEnv): string;
|
|
24
46
|
/** Native OMP title first, then a deterministic prompt/project fallback. */
|
|
25
47
|
export declare function ombSessionDisplayName(session: Pick<OmbSessionInfo, "project" | "id" | "nativeName" | "firstMessage" | "nativeSessionAvailable">): string;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the tmux-compatible multiplexer binary. `BIZAR_TMUX_BIN` always
|
|
50
|
+
* wins. On Windows, psmux is the supported native multiplexer (it ships a
|
|
51
|
+
* `tmux` command alias, but resolving `tmux` on PATH could pick unrelated
|
|
52
|
+
* binaries), so prefer `psmux` and fall back to the generic `tmux` name;
|
|
53
|
+
* elsewhere the standard `tmux` binary is used.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveMultiplexerBin(env?: NodeJS.ProcessEnv): string;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the hub state from tmux liveness, client attachment, and journal
|
|
58
|
+
* activity.
|
|
59
|
+
*
|
|
60
|
+
* Work wins over attachment: a detached client must never hide an agent that
|
|
61
|
+
* is still processing, which is the failure this hub exists to prevent.
|
|
62
|
+
* `waiting` refines an attached session that finished its turn, `connected`
|
|
63
|
+
* covers an attached session with no structured activity yet, and
|
|
64
|
+
* `disconnected` covers a live session with no client and no work in flight.
|
|
65
|
+
*/
|
|
66
|
+
export declare function ombSessionState(input: {
|
|
67
|
+
dead: boolean;
|
|
68
|
+
attached: boolean;
|
|
69
|
+
activity?: OmbSessionActivity;
|
|
70
|
+
}): OmbSessionState;
|
|
26
71
|
/** Discover Bizar-owned tmux sessions without touching unrelated tmux sessions. */
|
|
27
|
-
export declare function listOmbSessions(cwd?: string, env?: NodeJS.ProcessEnv): Promise<OmbSessionInfo[]>;
|
|
72
|
+
export declare function listOmbSessions(cwd?: string, env?: NodeJS.ProcessEnv, options?: ListOmbSessionsOptions): Promise<OmbSessionInfo[]>;
|
|
28
73
|
/** Return the most recently attached live session for one exact project path. */
|
|
29
74
|
export declare function findLastOmbSession(cwd: string, env?: NodeJS.ProcessEnv): Promise<OmbSessionInfo | undefined>;
|
|
30
75
|
//# sourceMappingURL=omb-sessions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"omb-sessions.d.ts","sourceRoot":"","sources":["../../src/omp/omb-sessions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"omb-sessions.d.ts","sourceRoot":"","sources":["../../src/omp/omb-sessions.ts"],"names":[],"mappings":"AAIA,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAK7C;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,CAAC;AAE/F,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,eAAe,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,oGAAoG;IACpG,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AA2BD,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE3F;AAuJD,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,IAAI,GAAG,YAAY,GAAG,cAAc,GAAG,wBAAwB,CAAC,GAAG,MAAM,CAOxJ;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;CAAE,GAAG,eAAe,CAM3H;AAkKD,mFAAmF;AACnF,wBAAsB,eAAe,CACnC,GAAG,SAAgB,EACnB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,cAAc,EAAE,CAAC,CAkB3B;AAED,iFAAiF;AACjF,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAOrC"}
|
package/dist/omp/omb-sessions.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { readdir,
|
|
2
|
+
import { readdir, stat } from "node:fs/promises";
|
|
3
3
|
import { basename, join, relative, resolve } from "node:path";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import { deriveSessionActivity, listSessionJournals, readJournalHead, readJournalTail, } from "./session-activity.js";
|
|
5
6
|
export const BIZAR_SESSION_PREFIX = "bizar-";
|
|
7
|
+
/** Most recent stopped sessions to surface when stopped sessions are enabled. */
|
|
8
|
+
const STOPPED_SESSION_LIMIT = 6;
|
|
9
|
+
function nativeSessionRoot(env) {
|
|
10
|
+
const stateHome = env.XDG_STATE_HOME || join(env.HOME || homedir(), ".local", "state");
|
|
11
|
+
return join(stateHome, "bizar-omp", "native-sessions");
|
|
12
|
+
}
|
|
6
13
|
/** Keep native OMP journals separate so each durable `omb` host has one exact session. */
|
|
7
14
|
export function ombSessionDir(session, env = process.env) {
|
|
8
|
-
|
|
9
|
-
return join(stateHome, "bizar-omp", "native-sessions", session);
|
|
15
|
+
return join(nativeSessionRoot(env), session);
|
|
10
16
|
}
|
|
11
17
|
function ompAgentDir(env) {
|
|
12
18
|
if (env.PI_CODING_AGENT_DIR)
|
|
@@ -38,86 +44,101 @@ function cleanText(value) {
|
|
|
38
44
|
.trim();
|
|
39
45
|
return cleaned || undefined;
|
|
40
46
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// A partially written final JSONL line should not hide a valid header/title.
|
|
75
|
-
}
|
|
47
|
+
function asRecord(value) {
|
|
48
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse one journal's session header, title, and first user prompt.
|
|
52
|
+
*
|
|
53
|
+
* Current journals open with a `title` entry that is rewritten in place, so
|
|
54
|
+
* the session header is the first `session` entry rather than the first line.
|
|
55
|
+
*/
|
|
56
|
+
async function readNativeSessionFile(journal) {
|
|
57
|
+
const content = await readJournalHead(journal);
|
|
58
|
+
if (!content)
|
|
59
|
+
return undefined;
|
|
60
|
+
let header;
|
|
61
|
+
const entries = [];
|
|
62
|
+
for (const line of content.split("\n")) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (!trimmed || trimmed[0] !== "{")
|
|
65
|
+
continue;
|
|
66
|
+
let entry;
|
|
67
|
+
try {
|
|
68
|
+
entry = JSON.parse(trimmed);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// A partially written final line must not hide the entries before it.
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!header && entry.type === "session") {
|
|
75
|
+
header = entry;
|
|
76
|
+
continue;
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
id: cleanText(header.id),
|
|
80
|
-
title: latestTitle,
|
|
81
|
-
cwd: cleanText(header.cwd),
|
|
82
|
-
firstMessage,
|
|
83
|
-
createdAt: Number.isFinite(timestamp) ? Math.floor(timestamp / 1000) : undefined,
|
|
84
|
-
};
|
|
78
|
+
entries.push(entry);
|
|
85
79
|
}
|
|
86
|
-
|
|
80
|
+
if (!header)
|
|
87
81
|
return undefined;
|
|
82
|
+
let latestTitle = cleanText(header.title);
|
|
83
|
+
let firstMessage;
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (entry.type === "title" || entry.type === "title_change")
|
|
86
|
+
latestTitle = cleanText(entry.title) || latestTitle;
|
|
87
|
+
if (firstMessage || entry.type !== "message")
|
|
88
|
+
continue;
|
|
89
|
+
const message = asRecord(entry.message);
|
|
90
|
+
if (message?.role !== "user")
|
|
91
|
+
continue;
|
|
92
|
+
if (typeof message.content === "string")
|
|
93
|
+
firstMessage = cleanText(message.content);
|
|
94
|
+
else if (Array.isArray(message.content)) {
|
|
95
|
+
firstMessage = cleanText(message.content
|
|
96
|
+
.filter(item => asRecord(item)?.type === "text")
|
|
97
|
+
.map(item => asRecord(item)?.text)
|
|
98
|
+
.filter((item) => typeof item === "string")
|
|
99
|
+
.join(" "));
|
|
100
|
+
}
|
|
88
101
|
}
|
|
102
|
+
const timestamp = typeof header.timestamp === "string" ? Date.parse(header.timestamp) : NaN;
|
|
103
|
+
return {
|
|
104
|
+
id: cleanText(header.id),
|
|
105
|
+
title: latestTitle,
|
|
106
|
+
cwd: cleanText(header.cwd),
|
|
107
|
+
firstMessage,
|
|
108
|
+
createdAt: Number.isFinite(timestamp) ? Math.floor(timestamp / 1000) : undefined,
|
|
109
|
+
};
|
|
89
110
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
/** Metadata plus live activity for a Bizar-owned native session directory. */
|
|
112
|
+
async function readNativeSessionSnapshot(sessionDir, now) {
|
|
113
|
+
const journals = await listSessionJournals(sessionDir);
|
|
114
|
+
const newest = journals.at(-1);
|
|
115
|
+
if (!newest)
|
|
116
|
+
return {};
|
|
117
|
+
const [entries, modifiedAt, ...candidates] = await Promise.all([
|
|
118
|
+
readJournalTail(newest),
|
|
119
|
+
stat(newest).then(file => file.mtimeMs).catch(() => undefined),
|
|
120
|
+
...journals.slice(-3).reverse().map(journal => readNativeSessionFile(journal)),
|
|
121
|
+
]);
|
|
122
|
+
let metadata;
|
|
123
|
+
for (const candidate of candidates) {
|
|
124
|
+
if (candidate) {
|
|
125
|
+
metadata = candidate;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
99
128
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
valid.sort((a, b) => a.file.localeCompare(b.file));
|
|
105
|
-
return valid.at(-1)?.metadata;
|
|
129
|
+
return {
|
|
130
|
+
metadata,
|
|
131
|
+
activity: entries.length > 0 ? deriveSessionActivity(entries, { now, fallbackUpdatedAt: modifiedAt }) : undefined,
|
|
132
|
+
};
|
|
106
133
|
}
|
|
107
134
|
async function readLegacyNativeSessionMetadata(cwd, sessionCreatedAt, env) {
|
|
108
135
|
const sessionDir = defaultOmpSessionDir(cwd, env);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
files = (await readdir(sessionDir, { withFileTypes: true }))
|
|
112
|
-
.filter(entry => entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
113
|
-
.map(entry => join(sessionDir, entry.name));
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
136
|
+
const journals = await listSessionJournals(sessionDir);
|
|
137
|
+
if (journals.length === 0)
|
|
116
138
|
return undefined;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return metadata ? { file, metadata } : undefined;
|
|
139
|
+
const candidates = (await Promise.all(journals.map(async (journal) => {
|
|
140
|
+
const metadata = await readNativeSessionFile(journal);
|
|
141
|
+
return metadata ? { journal, metadata } : undefined;
|
|
121
142
|
}))).filter((candidate) => Boolean(candidate));
|
|
122
143
|
if (candidates.length === 0)
|
|
123
144
|
return undefined;
|
|
@@ -154,6 +175,45 @@ export function ombSessionDisplayName(session) {
|
|
|
154
175
|
return `New ${session.project} session`;
|
|
155
176
|
return `${session.project}/${session.id || "main"}`;
|
|
156
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Resolve the tmux-compatible multiplexer binary. `BIZAR_TMUX_BIN` always
|
|
180
|
+
* wins. On Windows, psmux is the supported native multiplexer (it ships a
|
|
181
|
+
* `tmux` command alias, but resolving `tmux` on PATH could pick unrelated
|
|
182
|
+
* binaries), so prefer `psmux` and fall back to the generic `tmux` name;
|
|
183
|
+
* elsewhere the standard `tmux` binary is used.
|
|
184
|
+
*/
|
|
185
|
+
export function resolveMultiplexerBin(env = process.env) {
|
|
186
|
+
if (env.BIZAR_TMUX_BIN)
|
|
187
|
+
return env.BIZAR_TMUX_BIN;
|
|
188
|
+
return process.platform === "win32" ? "psmux" : "tmux";
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Resolve the hub state from tmux liveness, client attachment, and journal
|
|
192
|
+
* activity.
|
|
193
|
+
*
|
|
194
|
+
* Work wins over attachment: a detached client must never hide an agent that
|
|
195
|
+
* is still processing, which is the failure this hub exists to prevent.
|
|
196
|
+
* `waiting` refines an attached session that finished its turn, `connected`
|
|
197
|
+
* covers an attached session with no structured activity yet, and
|
|
198
|
+
* `disconnected` covers a live session with no client and no work in flight.
|
|
199
|
+
*/
|
|
200
|
+
export function ombSessionState(input) {
|
|
201
|
+
if (input.dead)
|
|
202
|
+
return "stopped";
|
|
203
|
+
const activity = input.activity;
|
|
204
|
+
if (activity && activity.phase !== "unknown" && !activity.awaitingInput)
|
|
205
|
+
return "working";
|
|
206
|
+
if (!input.attached)
|
|
207
|
+
return "disconnected";
|
|
208
|
+
return activity?.awaitingInput ? "waiting" : "connected";
|
|
209
|
+
}
|
|
210
|
+
const STATE_RANK = {
|
|
211
|
+
working: 0,
|
|
212
|
+
waiting: 1,
|
|
213
|
+
connected: 2,
|
|
214
|
+
disconnected: 3,
|
|
215
|
+
stopped: 4,
|
|
216
|
+
};
|
|
157
217
|
function exitCode(code, signal) {
|
|
158
218
|
if (code !== null)
|
|
159
219
|
return code;
|
|
@@ -165,7 +225,15 @@ function readTmux(command, args, cwd, env) {
|
|
|
165
225
|
child.stdout?.setEncoding("utf8");
|
|
166
226
|
child.stdout?.on("data", chunk => { output += chunk; });
|
|
167
227
|
return new Promise((resolve, reject) => {
|
|
168
|
-
|
|
228
|
+
// A missing multiplexer binary means "no sessions", not a crash:
|
|
229
|
+
// discovery callers treat any nonzero code as an empty listing. Any
|
|
230
|
+
// other spawn failure (EACCES, EPERM, ...) is still a hard error.
|
|
231
|
+
child.once("error", (error) => {
|
|
232
|
+
if (error.code === "ENOENT" || error.code === "EINVAL")
|
|
233
|
+
resolve({ code: 127, output: "" });
|
|
234
|
+
else
|
|
235
|
+
reject(error);
|
|
236
|
+
});
|
|
169
237
|
child.once("close", (code, signal) => resolve({ code: exitCode(code, signal), output }));
|
|
170
238
|
});
|
|
171
239
|
}
|
|
@@ -173,6 +241,11 @@ function sessionId(name) {
|
|
|
173
241
|
const match = name.match(/-[a-f0-9]{10}(?:-(.*))?$/);
|
|
174
242
|
return match?.[1] || "main";
|
|
175
243
|
}
|
|
244
|
+
/** Project slug encoded in the Bizar session name, without the digest. */
|
|
245
|
+
function sessionProject(name) {
|
|
246
|
+
const trimmed = name.slice(BIZAR_SESSION_PREFIX.length);
|
|
247
|
+
return trimmed.replace(/-[a-f0-9]{10}(?:-.*)?$/, "") || trimmed || "session";
|
|
248
|
+
}
|
|
176
249
|
function parsePane(output) {
|
|
177
250
|
const [dead, cwd, command, pid] = output.trim().split("\t");
|
|
178
251
|
return {
|
|
@@ -182,14 +255,8 @@ function parsePane(output) {
|
|
|
182
255
|
pid: pid || "?",
|
|
183
256
|
};
|
|
184
257
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const tmux = env.BIZAR_TMUX_BIN || "tmux";
|
|
188
|
-
const childEnv = { ...env, BIZAR_OMP_NO_TMUX: "1" };
|
|
189
|
-
const sessions = await readTmux(tmux, ["list-sessions", "-F", "#{session_name}\t#{session_created}\t#{session_last_attached}\t#{session_attached}"], cwd, childEnv);
|
|
190
|
-
if (sessions.code !== 0)
|
|
191
|
-
return [];
|
|
192
|
-
const names = sessions.output
|
|
258
|
+
function parseSessionList(output) {
|
|
259
|
+
return output
|
|
193
260
|
.split("\n")
|
|
194
261
|
.map(line => line.trim())
|
|
195
262
|
.filter(Boolean)
|
|
@@ -201,38 +268,99 @@ export async function listOmbSessions(cwd = process.cwd(), env = process.env) {
|
|
|
201
268
|
lastAttachedAt: Number.isFinite(Number(parts[2])) ? Number(parts[2]) : undefined,
|
|
202
269
|
attached: parts[3] !== "0",
|
|
203
270
|
}));
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
271
|
+
}
|
|
272
|
+
function sessionRecord(name, fields) {
|
|
273
|
+
return {
|
|
274
|
+
name,
|
|
275
|
+
project: fields.cwd === "?" ? sessionProject(name) : basename(fields.cwd),
|
|
276
|
+
id: sessionId(name),
|
|
277
|
+
...fields,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
async function describeLiveSession(tmux, session, cwd, childEnv, env, now) {
|
|
281
|
+
const pane = await readTmux(tmux, ["list-panes", "-t", session.name, "-F", "#{pane_dead}\t#{pane_current_path}\t#{pane_current_command}\t#{pane_pid}"], cwd, childEnv);
|
|
282
|
+
const details = parsePane(pane.output);
|
|
283
|
+
const nativeDir = ombSessionDir(session.name, env);
|
|
284
|
+
const snapshot = await readNativeSessionSnapshot(nativeDir, now);
|
|
285
|
+
const native = snapshot.metadata ?? await readLegacyNativeSessionMetadata(details.cwd, session.createdAt, env);
|
|
286
|
+
return sessionRecord(session.name, {
|
|
287
|
+
cwd: details.cwd,
|
|
288
|
+
state: ombSessionState({ dead: details.dead, attached: session.attached, activity: snapshot.activity }),
|
|
289
|
+
activity: snapshot.activity,
|
|
290
|
+
hosted: true,
|
|
291
|
+
command: details.command,
|
|
292
|
+
pid: details.pid,
|
|
293
|
+
nativeId: native?.id,
|
|
294
|
+
nativeName: native?.title,
|
|
295
|
+
firstMessage: native?.firstMessage,
|
|
296
|
+
nativeSessionAvailable: Boolean(snapshot.metadata || native),
|
|
297
|
+
createdAt: session.createdAt ?? native?.createdAt,
|
|
298
|
+
lastAttachedAt: session.lastAttachedAt,
|
|
229
299
|
});
|
|
230
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Bizar sessions that tmux no longer hosts.
|
|
303
|
+
*
|
|
304
|
+
* tmux removes a session as soon as its pane exits, so a terminated session
|
|
305
|
+
* only remains discoverable through the native state directory that `omb`
|
|
306
|
+
* gives every durable host. A journal written inside the stale window proves
|
|
307
|
+
* the agent is still running — a host started outside `omb`, or one whose pane
|
|
308
|
+
* died while the agent kept working — so those sessions stay live instead of
|
|
309
|
+
* being reported as stopped.
|
|
310
|
+
*/
|
|
311
|
+
async function listUnhostedSessions(env, liveNames, now) {
|
|
312
|
+
let entries;
|
|
313
|
+
try {
|
|
314
|
+
entries = await readdir(nativeSessionRoot(env), { withFileTypes: true });
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
const unhosted = (await Promise.all(entries.map(async (entry) => {
|
|
320
|
+
if (!entry.isDirectory() || !entry.name.startsWith(BIZAR_SESSION_PREFIX) || liveNames.has(entry.name))
|
|
321
|
+
return undefined;
|
|
322
|
+
const snapshot = await readNativeSessionSnapshot(join(nativeSessionRoot(env), entry.name), now);
|
|
323
|
+
const activity = snapshot.activity;
|
|
324
|
+
const alive = Boolean(activity && !activity.stale);
|
|
325
|
+
return sessionRecord(entry.name, {
|
|
326
|
+
cwd: snapshot.metadata?.cwd ?? "?",
|
|
327
|
+
state: alive ? ombSessionState({ dead: false, attached: false, activity }) : "stopped",
|
|
328
|
+
activity,
|
|
329
|
+
hosted: false,
|
|
330
|
+
command: alive ? "unattached" : "exited",
|
|
331
|
+
pid: "?",
|
|
332
|
+
nativeId: snapshot.metadata?.id,
|
|
333
|
+
nativeName: snapshot.metadata?.title,
|
|
334
|
+
firstMessage: snapshot.metadata?.firstMessage,
|
|
335
|
+
nativeSessionAvailable: Boolean(snapshot.metadata),
|
|
336
|
+
createdAt: snapshot.metadata?.createdAt,
|
|
337
|
+
});
|
|
338
|
+
}))).filter((session) => Boolean(session));
|
|
339
|
+
unhosted.sort((a, b) => (b.activity?.updatedAt ?? 0) - (a.activity?.updatedAt ?? 0));
|
|
340
|
+
const live = unhosted.filter(session => session.state !== "stopped");
|
|
341
|
+
return [...live, ...unhosted.filter(session => session.state === "stopped").slice(0, STOPPED_SESSION_LIMIT)];
|
|
342
|
+
}
|
|
343
|
+
/** Discover Bizar-owned tmux sessions without touching unrelated tmux sessions. */
|
|
344
|
+
export async function listOmbSessions(cwd = process.cwd(), env = process.env, options = {}) {
|
|
345
|
+
const tmux = resolveMultiplexerBin(env);
|
|
346
|
+
const childEnv = { ...env, BIZAR_OMP_NO_TMUX: "1" };
|
|
347
|
+
const now = Date.now();
|
|
348
|
+
const sessions = await readTmux(tmux, ["list-sessions", "-F", "#{session_name}\t#{session_created}\t#{session_last_attached}\t#{session_attached}"], cwd, childEnv);
|
|
349
|
+
const live = sessions.code === 0 ? parseSessionList(sessions.output) : [];
|
|
350
|
+
const records = await Promise.all(live.map(session => describeLiveSession(tmux, session, cwd, childEnv, env, now)));
|
|
351
|
+
const unhosted = await listUnhostedSessions(env, new Set(live.map(session => session.name)), now);
|
|
352
|
+
const stopped = options.includeStopped ? unhosted.filter(session => session.state === "stopped") : [];
|
|
353
|
+
return [...records, ...unhosted.filter(session => session.state !== "stopped"), ...stopped].sort((a, b) => STATE_RANK[a.state] - STATE_RANK[b.state] ||
|
|
354
|
+
a.project.localeCompare(b.project) ||
|
|
355
|
+
a.name.localeCompare(b.name));
|
|
356
|
+
}
|
|
231
357
|
/** Return the most recently attached live session for one exact project path. */
|
|
232
358
|
export async function findLastOmbSession(cwd, env = process.env) {
|
|
233
359
|
const sessions = await listOmbSessions(cwd, env);
|
|
234
360
|
return sessions
|
|
235
|
-
|
|
361
|
+
// Only a session tmux still hosts can be reattached. Resuming a live but
|
|
362
|
+
// unhosted session would put a second writer on its journal.
|
|
363
|
+
.filter(session => session.cwd === cwd && session.state !== "stopped" && session.hosted !== false)
|
|
236
364
|
.sort((a, b) => (b.lastAttachedAt ?? b.createdAt ?? 0) - (a.lastAttachedAt ?? a.createdAt ?? 0))[0];
|
|
237
365
|
}
|
|
238
366
|
//# sourceMappingURL=omb-sessions.js.map
|