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.
Files changed (123) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/bot-registry.d.ts +7 -0
  3. package/dist/bot-registry.d.ts.map +1 -1
  4. package/dist/bot-registry.js +46 -13
  5. package/dist/bot-registry.js.map +1 -1
  6. package/dist/core/config-dir.d.ts +167 -0
  7. package/dist/core/config-dir.d.ts.map +1 -0
  8. package/dist/core/config-dir.js +152 -0
  9. package/dist/core/config-dir.js.map +1 -0
  10. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  11. package/dist/core/dashboard-ipc-server.js +48 -0
  12. package/dist/core/dashboard-ipc-server.js.map +1 -1
  13. package/dist/core/per-bot-env.d.ts.map +1 -1
  14. package/dist/core/per-bot-env.js +9 -0
  15. package/dist/core/per-bot-env.js.map +1 -1
  16. package/dist/core/session-discovery.d.ts.map +1 -1
  17. package/dist/core/session-discovery.js +19 -2
  18. package/dist/core/session-discovery.js.map +1 -1
  19. package/dist/core/session-manager.d.ts.map +1 -1
  20. package/dist/core/session-manager.js +44 -3
  21. package/dist/core/session-manager.js.map +1 -1
  22. package/dist/core/trigger-session.d.ts.map +1 -1
  23. package/dist/core/trigger-session.js +499 -63
  24. package/dist/core/trigger-session.js.map +1 -1
  25. package/dist/core/types.d.ts +26 -4
  26. package/dist/core/types.d.ts.map +1 -1
  27. package/dist/core/types.js.map +1 -1
  28. package/dist/core/worker-pool.d.ts +64 -0
  29. package/dist/core/worker-pool.d.ts.map +1 -1
  30. package/dist/core/worker-pool.js +301 -10
  31. package/dist/core/worker-pool.js.map +1 -1
  32. package/dist/daemon.d.ts.map +1 -1
  33. package/dist/daemon.js +13 -6
  34. package/dist/daemon.js.map +1 -1
  35. package/dist/dashboard/auth.d.ts +8 -0
  36. package/dist/dashboard/auth.d.ts.map +1 -1
  37. package/dist/dashboard/auth.js +16 -11
  38. package/dist/dashboard/auth.js.map +1 -1
  39. package/dist/dashboard/web/bot-multi-select.d.ts +33 -0
  40. package/dist/dashboard/web/bot-multi-select.d.ts.map +1 -0
  41. package/dist/dashboard/web/bot-multi-select.js +34 -0
  42. package/dist/dashboard/web/bot-multi-select.js.map +1 -0
  43. package/dist/dashboard/web/groups-page.d.ts +13 -0
  44. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  45. package/dist/dashboard/web/groups-page.js +25 -7
  46. package/dist/dashboard/web/groups-page.js.map +1 -1
  47. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  48. package/dist/dashboard/web/i18n.js +8 -4
  49. package/dist/dashboard/web/i18n.js.map +1 -1
  50. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  51. package/dist/dashboard/web/sessions-page.js +15 -23
  52. package/dist/dashboard/web/sessions-page.js.map +1 -1
  53. package/dist/dashboard/web/sessions.d.ts +13 -0
  54. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  55. package/dist/dashboard/web/sessions.js +25 -0
  56. package/dist/dashboard/web/sessions.js.map +1 -1
  57. package/dist/dashboard-web/app.js +1 -1
  58. package/dist/dashboard-web/chunks/{bot-defaults-page-HL655CNR.js → bot-defaults-page-NQJFJ45Z.js} +1 -1
  59. package/dist/dashboard-web/chunks/{chunk-BFUTTYVK.js → chunk-5QJB24I2.js} +1 -1
  60. package/dist/dashboard-web/chunks/{chunk-ELL53D62.js → chunk-6JTZJRQJ.js} +2 -2
  61. package/dist/dashboard-web/chunks/{chunk-5ZL7NLR5.js → chunk-CU4SUKK3.js} +1 -1
  62. package/dist/dashboard-web/chunks/{chunk-5PAYQ3KT.js → chunk-KEEYQ3WF.js} +1 -1
  63. package/dist/dashboard-web/chunks/chunk-WLIVBPJC.js +1 -0
  64. package/dist/dashboard-web/chunks/{connectors-page-3QQACF23.js → connectors-page-HADHZXXZ.js} +1 -1
  65. package/dist/dashboard-web/chunks/groups-page-5TYF46RS.js +8 -0
  66. package/dist/dashboard-web/chunks/{insights-page-ZZ3JEJ6L.js → insights-page-EVGGJI5L.js} +1 -1
  67. package/dist/dashboard-web/chunks/{monitor-room-ROJ5GZ4N.js → monitor-room-HRGR2ENP.js} +1 -1
  68. package/dist/dashboard-web/chunks/{monitoring-page-IATV7KJF.js → monitoring-page-LBU7TTTW.js} +1 -1
  69. package/dist/dashboard-web/chunks/{office-page-TYO5OIIT.js → office-page-DF6RRBMN.js} +1 -1
  70. package/dist/dashboard-web/chunks/{overview-page-VJUULZF2.js → overview-page-ZB6QH2EX.js} +1 -1
  71. package/dist/dashboard-web/chunks/{roles-page-7RJJKZGK.js → roles-page-ZBZGHIIF.js} +1 -1
  72. package/dist/dashboard-web/chunks/{schedules-page-JTEJ5IXW.js → schedules-page-NQVQC3QY.js} +1 -1
  73. package/dist/dashboard-web/chunks/sessions-page-DXBCDWCS.js +8 -0
  74. package/dist/dashboard-web/chunks/{settings-page-7CTTFBRS.js → settings-page-E7NN4NVQ.js} +1 -1
  75. package/dist/dashboard-web/chunks/{skills-page-57NZECJ3.js → skills-page-2QAVB2SA.js} +1 -1
  76. package/dist/dashboard-web/chunks/{team-federation-page-6YYIMJG3.js → team-federation-page-VGWL53AE.js} +1 -1
  77. package/dist/dashboard-web/chunks/{v3-page-ZGKBSBK3.js → v3-page-AY35LKCX.js} +1 -1
  78. package/dist/dashboard-web/chunks/{whiteboards-page-AACJF6DB.js → whiteboards-page-BNWUU4KG.js} +1 -1
  79. package/dist/dashboard-web/style.css +61 -53
  80. package/dist/features/codex-notifier/internal-turn.d.ts +1 -2
  81. package/dist/features/codex-notifier/internal-turn.d.ts.map +1 -1
  82. package/dist/features/codex-notifier/internal-turn.js +1 -13
  83. package/dist/features/codex-notifier/internal-turn.js.map +1 -1
  84. package/dist/i18n/en.js +2 -2
  85. package/dist/i18n/en.js.map +1 -1
  86. package/dist/i18n/zh.js +2 -2
  87. package/dist/i18n/zh.js.map +1 -1
  88. package/dist/platform/secure-host-file.d.ts +63 -0
  89. package/dist/platform/secure-host-file.d.ts.map +1 -1
  90. package/dist/platform/secure-host-file.js +90 -25
  91. package/dist/platform/secure-host-file.js.map +1 -1
  92. package/dist/services/async-trigger-store.d.ts +24 -4
  93. package/dist/services/async-trigger-store.d.ts.map +1 -1
  94. package/dist/services/async-trigger-store.js +81 -19
  95. package/dist/services/async-trigger-store.js.map +1 -1
  96. package/dist/services/codex-session-meta.d.ts +4 -0
  97. package/dist/services/codex-session-meta.d.ts.map +1 -0
  98. package/dist/services/codex-session-meta.js +15 -0
  99. package/dist/services/codex-session-meta.js.map +1 -0
  100. package/dist/services/idempotency-store.d.ts +18 -3
  101. package/dist/services/idempotency-store.d.ts.map +1 -1
  102. package/dist/services/idempotency-store.js +30 -12
  103. package/dist/services/idempotency-store.js.map +1 -1
  104. package/dist/services/traex-transcript.d.ts +7 -5
  105. package/dist/services/traex-transcript.d.ts.map +1 -1
  106. package/dist/services/traex-transcript.js +139 -33
  107. package/dist/services/traex-transcript.js.map +1 -1
  108. package/dist/services/trigger-types.d.ts +14 -1
  109. package/dist/services/trigger-types.d.ts.map +1 -1
  110. package/dist/services/trigger-types.js +35 -0
  111. package/dist/services/trigger-types.js.map +1 -1
  112. package/dist/types.d.ts +2 -0
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/utils/child-env.d.ts +1 -1
  115. package/dist/utils/child-env.d.ts.map +1 -1
  116. package/dist/utils/child-env.js +18 -0
  117. package/dist/utils/child-env.js.map +1 -1
  118. package/dist/worker.js +35 -2
  119. package/dist/worker.js.map +1 -1
  120. package/package.json +1 -1
  121. package/dist/dashboard-web/chunks/chunk-6ALR252E.js +0 -1
  122. package/dist/dashboard-web/chunks/groups-page-SDZMJ7UV.js +0 -8
  123. 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,