botmux 3.12.1 → 3.13.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/dist/.runtime-build-id +1 -1
- package/dist/bot-registry.d.ts +7 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +46 -13
- package/dist/bot-registry.js.map +1 -1
- package/dist/core/config-dir.d.ts +167 -0
- package/dist/core/config-dir.d.ts.map +1 -0
- package/dist/core/config-dir.js +152 -0
- package/dist/core/config-dir.js.map +1 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +48 -0
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/per-bot-env.d.ts.map +1 -1
- package/dist/core/per-bot-env.js +9 -0
- package/dist/core/per-bot-env.js.map +1 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +19 -2
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +44 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +499 -63
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +26 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +64 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +301 -10
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +13 -6
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +8 -0
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +16 -11
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/web/bot-multi-select.d.ts +33 -0
- package/dist/dashboard/web/bot-multi-select.d.ts.map +1 -0
- package/dist/dashboard/web/bot-multi-select.js +34 -0
- package/dist/dashboard/web/bot-multi-select.js.map +1 -0
- package/dist/dashboard/web/groups-page.d.ts +13 -0
- package/dist/dashboard/web/groups-page.d.ts.map +1 -1
- package/dist/dashboard/web/groups-page.js +25 -7
- package/dist/dashboard/web/groups-page.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +8 -4
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +15 -23
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +13 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +25 -0
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/{bot-defaults-page-HL655CNR.js → bot-defaults-page-NQJFJ45Z.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-BFUTTYVK.js → chunk-5QJB24I2.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-ELL53D62.js → chunk-6JTZJRQJ.js} +2 -2
- package/dist/dashboard-web/chunks/{chunk-5ZL7NLR5.js → chunk-CU4SUKK3.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-5PAYQ3KT.js → chunk-KEEYQ3WF.js} +1 -1
- package/dist/dashboard-web/chunks/chunk-WLIVBPJC.js +1 -0
- package/dist/dashboard-web/chunks/{connectors-page-3QQACF23.js → connectors-page-HADHZXXZ.js} +1 -1
- package/dist/dashboard-web/chunks/groups-page-5TYF46RS.js +8 -0
- package/dist/dashboard-web/chunks/{insights-page-ZZ3JEJ6L.js → insights-page-EVGGJI5L.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-ROJ5GZ4N.js → monitor-room-HRGR2ENP.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-IATV7KJF.js → monitoring-page-LBU7TTTW.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-TYO5OIIT.js → office-page-DF6RRBMN.js} +1 -1
- package/dist/dashboard-web/chunks/{overview-page-VJUULZF2.js → overview-page-ZB6QH2EX.js} +1 -1
- package/dist/dashboard-web/chunks/{roles-page-7RJJKZGK.js → roles-page-ZBZGHIIF.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-JTEJ5IXW.js → schedules-page-NQVQC3QY.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-DXBCDWCS.js +8 -0
- package/dist/dashboard-web/chunks/{settings-page-7CTTFBRS.js → settings-page-E7NN4NVQ.js} +1 -1
- package/dist/dashboard-web/chunks/{skills-page-57NZECJ3.js → skills-page-2QAVB2SA.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-6YYIMJG3.js → team-federation-page-VGWL53AE.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-ZGKBSBK3.js → v3-page-AY35LKCX.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-AACJF6DB.js → whiteboards-page-BNWUU4KG.js} +1 -1
- package/dist/dashboard-web/style.css +61 -53
- package/dist/features/codex-notifier/internal-turn.d.ts +1 -2
- package/dist/features/codex-notifier/internal-turn.d.ts.map +1 -1
- package/dist/features/codex-notifier/internal-turn.js +1 -13
- package/dist/features/codex-notifier/internal-turn.js.map +1 -1
- package/dist/i18n/en.js +2 -2
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.js +2 -2
- package/dist/i18n/zh.js.map +1 -1
- package/dist/platform/secure-host-file.d.ts +63 -0
- package/dist/platform/secure-host-file.d.ts.map +1 -1
- package/dist/platform/secure-host-file.js +90 -25
- package/dist/platform/secure-host-file.js.map +1 -1
- package/dist/services/async-trigger-store.d.ts +24 -4
- package/dist/services/async-trigger-store.d.ts.map +1 -1
- package/dist/services/async-trigger-store.js +81 -19
- package/dist/services/async-trigger-store.js.map +1 -1
- package/dist/services/codex-session-meta.d.ts +4 -0
- package/dist/services/codex-session-meta.d.ts.map +1 -0
- package/dist/services/codex-session-meta.js +15 -0
- package/dist/services/codex-session-meta.js.map +1 -0
- package/dist/services/idempotency-store.d.ts +18 -3
- package/dist/services/idempotency-store.d.ts.map +1 -1
- package/dist/services/idempotency-store.js +30 -12
- package/dist/services/idempotency-store.js.map +1 -1
- package/dist/services/traex-transcript.d.ts +7 -5
- package/dist/services/traex-transcript.d.ts.map +1 -1
- package/dist/services/traex-transcript.js +139 -33
- package/dist/services/traex-transcript.js.map +1 -1
- package/dist/services/trigger-types.d.ts +14 -1
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js +35 -0
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/child-env.d.ts +1 -1
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +18 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/worker.js +35 -2
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/chunk-6ALR252E.js +0 -1
- package/dist/dashboard-web/chunks/groups-page-SDZMJ7UV.js +0 -8
- package/dist/dashboard-web/chunks/sessions-page-KD7HDT3U.js +0 -8
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve Botmux's configuration directory (`~/.botmux`) and the bots.json
|
|
3
|
+
* registry inside it, from ONE canonical precedence rule that matches the
|
|
4
|
+
* registry loader: `BOTS_CONFIG` (exact file) > `os.homedir()/.botmux/bots.json`.
|
|
5
|
+
*
|
|
6
|
+
* The home half is `os.homedir()` — deliberately the SAME single semantic
|
|
7
|
+
* `cli.ts` uses — NOT a hand-rolled `HOME`/`USERPROFILE` read, which forks from
|
|
8
|
+
* `cli.ts` on win32. See {@link resolveBotmuxConfigDir} for the platform contract.
|
|
9
|
+
*
|
|
10
|
+
* ── The bug this closes ──────────────────────────────────────────────────────
|
|
11
|
+
* `HOME=~/alt botmux start` makes the daemon load `~/alt/.botmux/bots.json`, but
|
|
12
|
+
* the daemon injects only `cwd` and the `BOTMUX_*` family into CLI children —
|
|
13
|
+
* never `HOME`. The child therefore resolves `homedir()/.botmux/bots.json` (the
|
|
14
|
+
* *default* home), does not find the bot it is running as, and every
|
|
15
|
+
* `botmux send` / `botmux history` from inside that session fails with
|
|
16
|
+
* `Bot not registered: <appId>`.
|
|
17
|
+
*
|
|
18
|
+
* ── Why the child is pinned to a FILE, not a DIRECTORY ───────────────────────
|
|
19
|
+
* The registry's real precedence is `BOTS_CONFIG` > `<config dir>/bots.json`,
|
|
20
|
+
* and `BOTS_CONFIG` may name an ARBITRARY file (`/srv/fleet-a.json`). A
|
|
21
|
+
* directory-shaped hint therefore cannot express what the daemon actually
|
|
22
|
+
* loaded: `dirname` + a hardcoded `bots.json` guesses wrong for every custom
|
|
23
|
+
* filename, and it sits BELOW `BOTS_CONFIG` in precedence, so a stale ambient
|
|
24
|
+
* `BOTS_CONFIG` in a shared tmux server's global env would silently outrank it
|
|
25
|
+
* and hand the child a foreign registry (verified: the child loaded the stale
|
|
26
|
+
* file while the daemon had the correct one).
|
|
27
|
+
*
|
|
28
|
+
* So the daemon pins the EXACT path it loaded — `getLoadedConfigPath()`, already
|
|
29
|
+
* frozen into `loadedBotsConfigPath` for the sandbox fs-policy — into the
|
|
30
|
+
* child's `BOTS_CONFIG`. That is one authority, at the TOP of the precedence
|
|
31
|
+
* chain, and file-shaped so any filename survives. `BOTS_CONFIG` is also
|
|
32
|
+
* reserved from per-bot `env` and scrubbed off the pane/tmux paths, because a
|
|
33
|
+
* bot must not be able to redirect the registry that defines it.
|
|
34
|
+
*
|
|
35
|
+
* Injecting `HOME` into children was considered and rejected: `HOME` also
|
|
36
|
+
* anchors the CLI's own config discovery (Claude Code falls back to
|
|
37
|
+
* `$HOME/.claude` when `CLAUDE_CONFIG_DIR` is unset), so overriding it to point
|
|
38
|
+
* at the fleet home silently relocates skills/settings for the spawned agent.
|
|
39
|
+
*
|
|
40
|
+
* ── Scope (deliberately narrow) ──────────────────────────────────────────────
|
|
41
|
+
* This is an INTERNAL daemon→child propagation fix, not a new public
|
|
42
|
+
* "relocate botmux" knob. `os.homedir()` already follows `$HOME`, so
|
|
43
|
+
* `HOME=~/alt botmux start` ALREADY relocates cli.ts's `CONFIG_DIR` / `DATA_DIR`
|
|
44
|
+
* / `PM2_HOME` / `BOTS_JSON_FILE`, the dashboard's write path and setup —
|
|
45
|
+
* verified: `HOME=<fleet> botmux setup list` reads the fleet registry with no
|
|
46
|
+
* code change. The daemon-spawned CLI child was the ONLY process that diverged,
|
|
47
|
+
* precisely because it is the only one that does not inherit `HOME`. Introducing
|
|
48
|
+
* a second, dir-shaped public variable would have created a rival source of
|
|
49
|
+
* truth that governs the registry but not the data dir, pm2 home or dashboard
|
|
50
|
+
* writes — half-relocated deployments — so no public variable is added.
|
|
51
|
+
*/
|
|
52
|
+
export interface ResolveBotmuxConfigDirOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Consulted for `BOTS_CONFIG` ONLY (see {@link resolveBotsConfigFile}) —
|
|
55
|
+
* never to derive the home directory. Reading `HOME`/`USERPROFILE` here would
|
|
56
|
+
* fork the config dir away from `cli.ts`'s `join(homedir(), '.botmux')` on
|
|
57
|
+
* win32; see {@link resolveBotmuxConfigDir}.
|
|
58
|
+
*/
|
|
59
|
+
env?: NodeJS.ProcessEnv;
|
|
60
|
+
/** Test seam ONLY (production passes nothing); defaults to os.homedir(). */
|
|
61
|
+
homeDir?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The env var naming the EXACT bots.json to load. Top of the registry's
|
|
65
|
+
* precedence chain, and the channel the daemon uses to pin its own loaded path
|
|
66
|
+
* onto a spawned CLI child.
|
|
67
|
+
*
|
|
68
|
+
* Reserved from per-bot `env` (see core/per-bot-env.ts) and stripped from the
|
|
69
|
+
* tmux client env + pane wrapper (see utils/child-env.ts): a bot must not be
|
|
70
|
+
* able to redirect the registry that defines it, and a stale value in a shared
|
|
71
|
+
* tmux server's global env must not reach a pane.
|
|
72
|
+
*/
|
|
73
|
+
export declare const BOTS_CONFIG_ENV = "BOTS_CONFIG";
|
|
74
|
+
/**
|
|
75
|
+
* `<home>/.botmux`, where `<home>` is `os.homedir()` — the SAME single semantic
|
|
76
|
+
* `cli.ts` uses for `CONFIG_DIR` / `DATA_DIR` / `PM2_HOME` / `BOTS_JSON_FILE`
|
|
77
|
+
* and the dashboard write path (`cli.ts`: `join(homedir(), '.botmux')`).
|
|
78
|
+
*
|
|
79
|
+
* ── Why NOT `env.HOME ?? env.USERPROFILE ?? homedir()` ───────────────────────
|
|
80
|
+
* Because `os.homedir()` is ALREADY the env-following rule, per platform, and
|
|
81
|
+
* re-deriving it by hand gets the platform wrong. Node's contract
|
|
82
|
+
* (https://nodejs.org/api/os.html#oshomedir):
|
|
83
|
+
* · POSIX — uses `$HOME` when set, else getpwuid(). So `HOME=~/alt botmux …`
|
|
84
|
+
* relocates the config dir with NO extra code; the hand-rolled `HOME` arm
|
|
85
|
+
* buys nothing here.
|
|
86
|
+
* · win32 — uses `%USERPROFILE%`; `HOME` is NOT consulted.
|
|
87
|
+
* A HOME-first rule therefore FORKS from `cli.ts` on win32 whenever `HOME` and
|
|
88
|
+
* `USERPROFILE` are both set to different values (Git-for-Windows/MSYS shells set
|
|
89
|
+
* `HOME`): `setup`/`start`/PM2 would write `%USERPROFILE%\.botmux` while the
|
|
90
|
+
* daemon's registry read `%HOME%\.botmux` — reconstructing the very daemon/child
|
|
91
|
+
* registry split this module exists to close, on a platform the repo supports
|
|
92
|
+
* (win32 PM2 / Task Scheduler / `.cmd` wrapper paths). master's bot-registry used
|
|
93
|
+
* bare `homedir()` and agreed with `cli.ts`, so a HOME-first rule here would be a
|
|
94
|
+
* REGRESSION, not a new feature.
|
|
95
|
+
*
|
|
96
|
+
* A hand-rolled env read is also wrong in a platform-INDEPENDENT way: `??` is
|
|
97
|
+
* nullish, so `HOME=''` (which does occur in stripped service environments) is
|
|
98
|
+
* accepted as a real value and `join('', '.botmux')` yields the RELATIVE,
|
|
99
|
+
* cwd-dependent `.botmux`. `homedir()` has no such hole on POSIX — an empty
|
|
100
|
+
* `$HOME` falls through to getpwuid().
|
|
101
|
+
*
|
|
102
|
+
* Custom homes in TESTS go through the `homeDir` seam, never through env.
|
|
103
|
+
*
|
|
104
|
+
* Note this resolves the DIRECTORY only, and is therefore NOT the whole story
|
|
105
|
+
* for the registry: `BOTS_CONFIG` may point at an arbitrary file outside this
|
|
106
|
+
* dir. Use {@link resolveBotsConfigFile} whenever you need the registry path.
|
|
107
|
+
*/
|
|
108
|
+
export declare function resolveBotmuxConfigDir(options?: ResolveBotmuxConfigDirOptions): string;
|
|
109
|
+
/**
|
|
110
|
+
* The bots.json path implied by the environment: `BOTS_CONFIG` (absolute-ized
|
|
111
|
+
* against cwd, exactly as the loader does) else `<config dir>/bots.json`.
|
|
112
|
+
*
|
|
113
|
+
* `env` is consulted for `BOTS_CONFIG` ONLY. The home half comes from
|
|
114
|
+
* {@link resolveBotmuxConfigDir} (i.e. `os.homedir()`), so this never re-derives
|
|
115
|
+
* a home from `HOME`/`USERPROFILE` and cannot fork from `cli.ts` on win32.
|
|
116
|
+
*
|
|
117
|
+
* Existence is NOT checked here — callers differ on what an absent file means
|
|
118
|
+
* (the loader throws for an explicit `BOTS_CONFIG`, degrades for the default).
|
|
119
|
+
*/
|
|
120
|
+
export declare function resolveBotsConfigFile(options?: ResolveBotmuxConfigDirOptions): string;
|
|
121
|
+
/**
|
|
122
|
+
* Where a `loadedBotsConfigPath` came from. This is the PROVENANCE the pin
|
|
123
|
+
* decision turns on, and it is carried explicitly because it is NOT derivable
|
|
124
|
+
* from the path or from the filesystem:
|
|
125
|
+
*
|
|
126
|
+
* · `'loaded'` — the daemon actually opened and parsed this exact file
|
|
127
|
+
* (`resolveBotConfigPath` set it). It is the registry authority.
|
|
128
|
+
* · `'synthetic'` — nothing was parsed. Core-only synthesis
|
|
129
|
+
* (`maybeSynthesizeCoreOnlyConfig`) pins the DEFAULT
|
|
130
|
+
* `<config dir>/bots.json` purely so the no-transport fs-policy
|
|
131
|
+
* sees the config inside the default authority root; that file
|
|
132
|
+
* is ignored by design and may not even exist.
|
|
133
|
+
*
|
|
134
|
+
* An `existsSync` probe cannot stand in for this:
|
|
135
|
+
* existence and provenance are independent, so the probe is wrong in BOTH
|
|
136
|
+
* directions. A real loaded file that later vanished reads as "absent" and the
|
|
137
|
+
* pin gets dropped (the child then silently loads a FOREIGN registry from its own
|
|
138
|
+
* HOME); a synthetic placeholder that happens to exist reads as "present" and
|
|
139
|
+
* gets pinned even though the daemon never parsed it.
|
|
140
|
+
*/
|
|
141
|
+
export type BotsConfigProvenance = 'loaded' | 'synthetic';
|
|
142
|
+
/**
|
|
143
|
+
* Decide the `BOTS_CONFIG` value to pin onto a spawned CLI child.
|
|
144
|
+
*
|
|
145
|
+
* The rule is provenance-driven, NOT existence-driven:
|
|
146
|
+
*
|
|
147
|
+
* · provenance `'loaded'` → pin UNCONDITIONALLY (absolute-ized, since daemon /
|
|
148
|
+
* worker / pane share no cwd). Even if the file has since vanished, the pin
|
|
149
|
+
* stays: the child must then FAIL LOUDLY in the loader
|
|
150
|
+
* (`BOTS_CONFIG file not found`, bot-registry.ts) rather than silently switch
|
|
151
|
+
* authority to whatever `<its own HOME>/.botmux/bots.json` contains. Under a
|
|
152
|
+
* multi-fleet non-default HOME that fallback is a DIFFERENT registry — same
|
|
153
|
+
* appId can carry another fleet's secret and another oncall routing — so
|
|
154
|
+
* "fail closed" is strictly correct and "degrade gracefully" is the bug.
|
|
155
|
+
* (A read-ISOLATED child still works: Seatbelt allows the metadata read, so
|
|
156
|
+
* the loader's `existsSync` passes and its `EPERM + underReadIsolation`
|
|
157
|
+
* branch takes over.)
|
|
158
|
+
*
|
|
159
|
+
* · provenance `'synthetic'` (or no path at all) → return null, and the caller
|
|
160
|
+
* must DELETE `BOTS_CONFIG` from the child env rather than leave an inherited
|
|
161
|
+
* value: `BOTS_CONFIG` is the TOP of the precedence chain, so a stale ambient
|
|
162
|
+
* value would outrank the on-disk default and hand the child a foreign
|
|
163
|
+
* registry. Omitting it is right here precisely because nothing was parsed —
|
|
164
|
+
* there is no authority to propagate.
|
|
165
|
+
*/
|
|
166
|
+
export declare function resolveChildBotsConfig(loadedConfigPath: string | undefined, provenance: BotsConfigProvenance | undefined): string | null;
|
|
167
|
+
//# sourceMappingURL=config-dir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-dir.d.ts","sourceRoot":"","sources":["../../src/core/config-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAKH,MAAM,WAAW,6BAA6B;IAC5C;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,MAAM,CAER;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,6BAAkC,GAC1C,MAAM,CAKR;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CACpC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,UAAU,EAAE,oBAAoB,GAAG,SAAS,GAC3C,MAAM,GAAG,IAAI,CAKf"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve Botmux's configuration directory (`~/.botmux`) and the bots.json
|
|
3
|
+
* registry inside it, from ONE canonical precedence rule that matches the
|
|
4
|
+
* registry loader: `BOTS_CONFIG` (exact file) > `os.homedir()/.botmux/bots.json`.
|
|
5
|
+
*
|
|
6
|
+
* The home half is `os.homedir()` — deliberately the SAME single semantic
|
|
7
|
+
* `cli.ts` uses — NOT a hand-rolled `HOME`/`USERPROFILE` read, which forks from
|
|
8
|
+
* `cli.ts` on win32. See {@link resolveBotmuxConfigDir} for the platform contract.
|
|
9
|
+
*
|
|
10
|
+
* ── The bug this closes ──────────────────────────────────────────────────────
|
|
11
|
+
* `HOME=~/alt botmux start` makes the daemon load `~/alt/.botmux/bots.json`, but
|
|
12
|
+
* the daemon injects only `cwd` and the `BOTMUX_*` family into CLI children —
|
|
13
|
+
* never `HOME`. The child therefore resolves `homedir()/.botmux/bots.json` (the
|
|
14
|
+
* *default* home), does not find the bot it is running as, and every
|
|
15
|
+
* `botmux send` / `botmux history` from inside that session fails with
|
|
16
|
+
* `Bot not registered: <appId>`.
|
|
17
|
+
*
|
|
18
|
+
* ── Why the child is pinned to a FILE, not a DIRECTORY ───────────────────────
|
|
19
|
+
* The registry's real precedence is `BOTS_CONFIG` > `<config dir>/bots.json`,
|
|
20
|
+
* and `BOTS_CONFIG` may name an ARBITRARY file (`/srv/fleet-a.json`). A
|
|
21
|
+
* directory-shaped hint therefore cannot express what the daemon actually
|
|
22
|
+
* loaded: `dirname` + a hardcoded `bots.json` guesses wrong for every custom
|
|
23
|
+
* filename, and it sits BELOW `BOTS_CONFIG` in precedence, so a stale ambient
|
|
24
|
+
* `BOTS_CONFIG` in a shared tmux server's global env would silently outrank it
|
|
25
|
+
* and hand the child a foreign registry (verified: the child loaded the stale
|
|
26
|
+
* file while the daemon had the correct one).
|
|
27
|
+
*
|
|
28
|
+
* So the daemon pins the EXACT path it loaded — `getLoadedConfigPath()`, already
|
|
29
|
+
* frozen into `loadedBotsConfigPath` for the sandbox fs-policy — into the
|
|
30
|
+
* child's `BOTS_CONFIG`. That is one authority, at the TOP of the precedence
|
|
31
|
+
* chain, and file-shaped so any filename survives. `BOTS_CONFIG` is also
|
|
32
|
+
* reserved from per-bot `env` and scrubbed off the pane/tmux paths, because a
|
|
33
|
+
* bot must not be able to redirect the registry that defines it.
|
|
34
|
+
*
|
|
35
|
+
* Injecting `HOME` into children was considered and rejected: `HOME` also
|
|
36
|
+
* anchors the CLI's own config discovery (Claude Code falls back to
|
|
37
|
+
* `$HOME/.claude` when `CLAUDE_CONFIG_DIR` is unset), so overriding it to point
|
|
38
|
+
* at the fleet home silently relocates skills/settings for the spawned agent.
|
|
39
|
+
*
|
|
40
|
+
* ── Scope (deliberately narrow) ──────────────────────────────────────────────
|
|
41
|
+
* This is an INTERNAL daemon→child propagation fix, not a new public
|
|
42
|
+
* "relocate botmux" knob. `os.homedir()` already follows `$HOME`, so
|
|
43
|
+
* `HOME=~/alt botmux start` ALREADY relocates cli.ts's `CONFIG_DIR` / `DATA_DIR`
|
|
44
|
+
* / `PM2_HOME` / `BOTS_JSON_FILE`, the dashboard's write path and setup —
|
|
45
|
+
* verified: `HOME=<fleet> botmux setup list` reads the fleet registry with no
|
|
46
|
+
* code change. The daemon-spawned CLI child was the ONLY process that diverged,
|
|
47
|
+
* precisely because it is the only one that does not inherit `HOME`. Introducing
|
|
48
|
+
* a second, dir-shaped public variable would have created a rival source of
|
|
49
|
+
* truth that governs the registry but not the data dir, pm2 home or dashboard
|
|
50
|
+
* writes — half-relocated deployments — so no public variable is added.
|
|
51
|
+
*/
|
|
52
|
+
import { homedir } from 'node:os';
|
|
53
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
54
|
+
/**
|
|
55
|
+
* The env var naming the EXACT bots.json to load. Top of the registry's
|
|
56
|
+
* precedence chain, and the channel the daemon uses to pin its own loaded path
|
|
57
|
+
* onto a spawned CLI child.
|
|
58
|
+
*
|
|
59
|
+
* Reserved from per-bot `env` (see core/per-bot-env.ts) and stripped from the
|
|
60
|
+
* tmux client env + pane wrapper (see utils/child-env.ts): a bot must not be
|
|
61
|
+
* able to redirect the registry that defines it, and a stale value in a shared
|
|
62
|
+
* tmux server's global env must not reach a pane.
|
|
63
|
+
*/
|
|
64
|
+
export const BOTS_CONFIG_ENV = 'BOTS_CONFIG';
|
|
65
|
+
/**
|
|
66
|
+
* `<home>/.botmux`, where `<home>` is `os.homedir()` — the SAME single semantic
|
|
67
|
+
* `cli.ts` uses for `CONFIG_DIR` / `DATA_DIR` / `PM2_HOME` / `BOTS_JSON_FILE`
|
|
68
|
+
* and the dashboard write path (`cli.ts`: `join(homedir(), '.botmux')`).
|
|
69
|
+
*
|
|
70
|
+
* ── Why NOT `env.HOME ?? env.USERPROFILE ?? homedir()` ───────────────────────
|
|
71
|
+
* Because `os.homedir()` is ALREADY the env-following rule, per platform, and
|
|
72
|
+
* re-deriving it by hand gets the platform wrong. Node's contract
|
|
73
|
+
* (https://nodejs.org/api/os.html#oshomedir):
|
|
74
|
+
* · POSIX — uses `$HOME` when set, else getpwuid(). So `HOME=~/alt botmux …`
|
|
75
|
+
* relocates the config dir with NO extra code; the hand-rolled `HOME` arm
|
|
76
|
+
* buys nothing here.
|
|
77
|
+
* · win32 — uses `%USERPROFILE%`; `HOME` is NOT consulted.
|
|
78
|
+
* A HOME-first rule therefore FORKS from `cli.ts` on win32 whenever `HOME` and
|
|
79
|
+
* `USERPROFILE` are both set to different values (Git-for-Windows/MSYS shells set
|
|
80
|
+
* `HOME`): `setup`/`start`/PM2 would write `%USERPROFILE%\.botmux` while the
|
|
81
|
+
* daemon's registry read `%HOME%\.botmux` — reconstructing the very daemon/child
|
|
82
|
+
* registry split this module exists to close, on a platform the repo supports
|
|
83
|
+
* (win32 PM2 / Task Scheduler / `.cmd` wrapper paths). master's bot-registry used
|
|
84
|
+
* bare `homedir()` and agreed with `cli.ts`, so a HOME-first rule here would be a
|
|
85
|
+
* REGRESSION, not a new feature.
|
|
86
|
+
*
|
|
87
|
+
* A hand-rolled env read is also wrong in a platform-INDEPENDENT way: `??` is
|
|
88
|
+
* nullish, so `HOME=''` (which does occur in stripped service environments) is
|
|
89
|
+
* accepted as a real value and `join('', '.botmux')` yields the RELATIVE,
|
|
90
|
+
* cwd-dependent `.botmux`. `homedir()` has no such hole on POSIX — an empty
|
|
91
|
+
* `$HOME` falls through to getpwuid().
|
|
92
|
+
*
|
|
93
|
+
* Custom homes in TESTS go through the `homeDir` seam, never through env.
|
|
94
|
+
*
|
|
95
|
+
* Note this resolves the DIRECTORY only, and is therefore NOT the whole story
|
|
96
|
+
* for the registry: `BOTS_CONFIG` may point at an arbitrary file outside this
|
|
97
|
+
* dir. Use {@link resolveBotsConfigFile} whenever you need the registry path.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveBotmuxConfigDir(options = {}) {
|
|
100
|
+
return join(options.homeDir ?? homedir(), '.botmux');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The bots.json path implied by the environment: `BOTS_CONFIG` (absolute-ized
|
|
104
|
+
* against cwd, exactly as the loader does) else `<config dir>/bots.json`.
|
|
105
|
+
*
|
|
106
|
+
* `env` is consulted for `BOTS_CONFIG` ONLY. The home half comes from
|
|
107
|
+
* {@link resolveBotmuxConfigDir} (i.e. `os.homedir()`), so this never re-derives
|
|
108
|
+
* a home from `HOME`/`USERPROFILE` and cannot fork from `cli.ts` on win32.
|
|
109
|
+
*
|
|
110
|
+
* Existence is NOT checked here — callers differ on what an absent file means
|
|
111
|
+
* (the loader throws for an explicit `BOTS_CONFIG`, degrades for the default).
|
|
112
|
+
*/
|
|
113
|
+
export function resolveBotsConfigFile(options = {}) {
|
|
114
|
+
const env = options.env ?? process.env;
|
|
115
|
+
const explicit = env[BOTS_CONFIG_ENV]?.trim();
|
|
116
|
+
if (explicit)
|
|
117
|
+
return resolve(explicit);
|
|
118
|
+
return join(resolveBotmuxConfigDir(options), 'bots.json');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Decide the `BOTS_CONFIG` value to pin onto a spawned CLI child.
|
|
122
|
+
*
|
|
123
|
+
* The rule is provenance-driven, NOT existence-driven:
|
|
124
|
+
*
|
|
125
|
+
* · provenance `'loaded'` → pin UNCONDITIONALLY (absolute-ized, since daemon /
|
|
126
|
+
* worker / pane share no cwd). Even if the file has since vanished, the pin
|
|
127
|
+
* stays: the child must then FAIL LOUDLY in the loader
|
|
128
|
+
* (`BOTS_CONFIG file not found`, bot-registry.ts) rather than silently switch
|
|
129
|
+
* authority to whatever `<its own HOME>/.botmux/bots.json` contains. Under a
|
|
130
|
+
* multi-fleet non-default HOME that fallback is a DIFFERENT registry — same
|
|
131
|
+
* appId can carry another fleet's secret and another oncall routing — so
|
|
132
|
+
* "fail closed" is strictly correct and "degrade gracefully" is the bug.
|
|
133
|
+
* (A read-ISOLATED child still works: Seatbelt allows the metadata read, so
|
|
134
|
+
* the loader's `existsSync` passes and its `EPERM + underReadIsolation`
|
|
135
|
+
* branch takes over.)
|
|
136
|
+
*
|
|
137
|
+
* · provenance `'synthetic'` (or no path at all) → return null, and the caller
|
|
138
|
+
* must DELETE `BOTS_CONFIG` from the child env rather than leave an inherited
|
|
139
|
+
* value: `BOTS_CONFIG` is the TOP of the precedence chain, so a stale ambient
|
|
140
|
+
* value would outrank the on-disk default and hand the child a foreign
|
|
141
|
+
* registry. Omitting it is right here precisely because nothing was parsed —
|
|
142
|
+
* there is no authority to propagate.
|
|
143
|
+
*/
|
|
144
|
+
export function resolveChildBotsConfig(loadedConfigPath, provenance) {
|
|
145
|
+
if (provenance !== 'loaded')
|
|
146
|
+
return null;
|
|
147
|
+
const trimmed = loadedConfigPath?.trim();
|
|
148
|
+
if (!trimmed)
|
|
149
|
+
return null;
|
|
150
|
+
return isAbsolute(trimmed) ? trimmed : resolve(trimmed);
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=config-dir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-dir.js","sourceRoot":"","sources":["../../src/core/config-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AActD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAyC,EAAE;IAE3C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAyC,EAAE;IAE3C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC;IAC9C,IAAI,QAAQ;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC;AAC5D,CAAC;AAwBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,sBAAsB,CACpC,gBAAoC,EACpC,UAA4C;IAE5C,IAAI,UAAU,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,OAAO,GAAG,gBAAgB,EAAE,IAAI,EAAE,CAAC;IACzC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-ipc-server.d.ts","sourceRoot":"","sources":["../../src/core/dashboard-ipc-server.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dashboard-ipc-server.d.ts","sourceRoot":"","sources":["../../src/core/dashboard-ipc-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -1418,6 +1418,54 @@ function buildAsyncTriggerLookupResponse(sessionId, triggerId) {
|
|
|
1418
1418
|
}
|
|
1419
1419
|
const memTriggerId = triggerId || ds?.latestAsyncTriggerId;
|
|
1420
1420
|
const memResult = ds && memTriggerId ? ds.asyncTriggerResults?.get(memTriggerId) : undefined;
|
|
1421
|
+
// Best-effort poll-side convergence for the double-fault turn (codex #818 P1-8):
|
|
1422
|
+
// the CONTRACT is that a double-fault returns 5xx and the caller retries with the
|
|
1423
|
+
// same key (that retry path is the authoritative recovery). This block is only a
|
|
1424
|
+
// bonus for a client that happens to poll first — if this turn is flagged
|
|
1425
|
+
// postBarrierFault (post-barrier throw AND the durable terminalize then threw),
|
|
1426
|
+
// nothing dispatched and no durable result exists, yet a live shared worker keeps
|
|
1427
|
+
// `liveActive` true so resolveAsyncTriggerState would otherwise report `running`.
|
|
1428
|
+
// Re-attempt the strict terminalize opportunistically; a persistent EIO simply
|
|
1429
|
+
// falls through to `running` and is converged by a same-key retry or boot reconcile.
|
|
1430
|
+
if (ds && memTriggerId) {
|
|
1431
|
+
const faultEntry = ds.idempotentAsyncTurns?.get(memTriggerId);
|
|
1432
|
+
if (faultEntry?.postBarrierFault) {
|
|
1433
|
+
// COMPLETED-WINS (codex #818 P1-8 race): the AUTHORITATIVE decision is
|
|
1434
|
+
// recordFailedStrict's IN-LOCK outcome (no TOCTOU). A pre-read fast-path
|
|
1435
|
+
// avoids the write when already visibly completed; else the in-lock return
|
|
1436
|
+
// (`already_completed` vs `written_failed`) decides. Never terminalize over a
|
|
1437
|
+
// completion that landed after the pre-read.
|
|
1438
|
+
const durable = asyncTriggerStore.lookup(sessionId, memTriggerId);
|
|
1439
|
+
const ownedCompleted = durable?.result.status === 'completed'
|
|
1440
|
+
&& durable.ownerLarkAppId === faultEntry.ownerLarkAppId;
|
|
1441
|
+
if (ownedCompleted) {
|
|
1442
|
+
ds.idempotentAsyncTurns?.delete(memTriggerId);
|
|
1443
|
+
// fall through to normal resolution → reports completed.
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
try {
|
|
1447
|
+
const outcome = asyncTriggerStore.recordFailedStrict(sessionId, memTriggerId, Date.now(), faultEntry.ownerLarkAppId, 'dispatch_unknown');
|
|
1448
|
+
ds.idempotentAsyncTurns?.delete(memTriggerId);
|
|
1449
|
+
if (outcome === 'written_failed') {
|
|
1450
|
+
ds.asyncTriggerResults?.delete(memTriggerId);
|
|
1451
|
+
return {
|
|
1452
|
+
ok: true, state: 'failed', triggerId: memTriggerId,
|
|
1453
|
+
target: { kind: 'turn', sessionId, chatId: ds.chatId ?? stored?.chatId },
|
|
1454
|
+
errorCode: 'no_output',
|
|
1455
|
+
error: 'previous dispatch was interrupted with unknown outcome; not re-run (at-most-once)',
|
|
1456
|
+
message: 'async trigger terminated without output',
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
// outcome === 'already_completed': a completion was seen under the lock —
|
|
1460
|
+
// fall through to normal resolution (which reports the completed result).
|
|
1461
|
+
}
|
|
1462
|
+
catch {
|
|
1463
|
+
// Genuine I/O failure — leave the flag for the next poll / retry / boot
|
|
1464
|
+
// reconcile; fall through to `running` rather than a phantom terminal.
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1421
1469
|
const resolved = resolveAsyncTriggerState({
|
|
1422
1470
|
sessionId,
|
|
1423
1471
|
liveActive: !!ds,
|