@berthdev/berth 0.3.5 → 0.4.2

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 (136) hide show
  1. package/README.md +7 -1
  2. package/dist/src/agents/capabilities.d.ts +13 -0
  3. package/dist/src/agents/capabilities.js +52 -0
  4. package/dist/src/agents/capabilities.js.map +1 -0
  5. package/dist/src/agents/codex-binding.d.ts +10 -0
  6. package/dist/src/agents/codex-binding.js +105 -0
  7. package/dist/src/agents/codex-binding.js.map +1 -0
  8. package/dist/src/agents/commands.d.ts +4 -2
  9. package/dist/src/agents/commands.js +41 -9
  10. package/dist/src/agents/commands.js.map +1 -1
  11. package/dist/src/agents/copilot-state.d.ts +3 -0
  12. package/dist/src/agents/copilot-state.js +94 -0
  13. package/dist/src/agents/copilot-state.js.map +1 -0
  14. package/dist/src/agents/gemini-state.d.ts +3 -0
  15. package/dist/src/agents/gemini-state.js +41 -0
  16. package/dist/src/agents/gemini-state.js.map +1 -0
  17. package/dist/src/agents/hook-files.d.ts +1 -3
  18. package/dist/src/agents/hook-files.js +69 -53
  19. package/dist/src/agents/hook-files.js.map +1 -1
  20. package/dist/src/agents/hook-health.d.ts +4 -0
  21. package/dist/src/agents/hook-health.js +95 -0
  22. package/dist/src/agents/hook-health.js.map +1 -0
  23. package/dist/src/agents/hooks.d.ts +4 -1
  24. package/dist/src/agents/hooks.js +155 -22
  25. package/dist/src/agents/hooks.js.map +1 -1
  26. package/dist/src/agents/jsonl-titles.d.ts +8 -0
  27. package/dist/src/agents/jsonl-titles.js +124 -0
  28. package/dist/src/agents/jsonl-titles.js.map +1 -0
  29. package/dist/src/agents/late-binding.d.ts +2 -0
  30. package/dist/src/agents/late-binding.js +25 -0
  31. package/dist/src/agents/late-binding.js.map +1 -0
  32. package/dist/src/agents/profile.d.ts +14 -0
  33. package/dist/src/agents/profile.js +75 -0
  34. package/dist/src/agents/profile.js.map +1 -0
  35. package/dist/src/agents/registry.d.ts +12 -3
  36. package/dist/src/agents/registry.js +52 -19
  37. package/dist/src/agents/registry.js.map +1 -1
  38. package/dist/src/agents/roots.d.ts +2 -0
  39. package/dist/src/agents/roots.js +21 -0
  40. package/dist/src/agents/roots.js.map +1 -0
  41. package/dist/src/agents/titles.d.ts +14 -0
  42. package/dist/src/agents/titles.js +82 -0
  43. package/dist/src/agents/titles.js.map +1 -0
  44. package/dist/src/agents/transcript-cursor.d.ts +9 -0
  45. package/dist/src/agents/transcript-cursor.js +86 -0
  46. package/dist/src/agents/transcript-cursor.js.map +1 -0
  47. package/dist/src/cli/main.js +40 -7
  48. package/dist/src/cli/main.js.map +1 -1
  49. package/dist/src/config/index.d.ts +6 -0
  50. package/dist/src/config/index.js +37 -6
  51. package/dist/src/config/index.js.map +1 -1
  52. package/dist/src/config/write.d.ts +2 -0
  53. package/dist/src/config/write.js +41 -0
  54. package/dist/src/config/write.js.map +1 -0
  55. package/dist/src/context.js +1 -1
  56. package/dist/src/context.js.map +1 -1
  57. package/dist/src/index.d.ts +11 -5
  58. package/dist/src/index.js +10 -4
  59. package/dist/src/index.js.map +1 -1
  60. package/dist/src/iterm2/install.d.ts +2 -0
  61. package/dist/src/iterm2/install.js +37 -12
  62. package/dist/src/iterm2/install.js.map +1 -1
  63. package/dist/src/present/attach.js +17 -5
  64. package/dist/src/present/attach.js.map +1 -1
  65. package/dist/src/present/state.d.ts +3 -0
  66. package/dist/src/present/state.js +12 -8
  67. package/dist/src/present/state.js.map +1 -1
  68. package/dist/src/process-arguments.d.ts +2 -0
  69. package/dist/src/process-arguments.js +59 -0
  70. package/dist/src/process-arguments.js.map +1 -0
  71. package/dist/src/process-directory.d.ts +2 -0
  72. package/dist/src/process-directory.js +37 -0
  73. package/dist/src/process-directory.js.map +1 -0
  74. package/dist/src/process-owner.d.ts +7 -0
  75. package/dist/src/process-owner.js +51 -0
  76. package/dist/src/process-owner.js.map +1 -0
  77. package/dist/src/registry/directory.d.ts +2 -0
  78. package/dist/src/registry/directory.js +19 -0
  79. package/dist/src/registry/directory.js.map +1 -0
  80. package/dist/src/registry/discover.d.ts +15 -5
  81. package/dist/src/registry/discover.js +135 -38
  82. package/dist/src/registry/discover.js.map +1 -1
  83. package/dist/src/registry/doctor.d.ts +5 -1
  84. package/dist/src/registry/doctor.js +53 -12
  85. package/dist/src/registry/doctor.js.map +1 -1
  86. package/dist/src/registry/hosts.d.ts +28 -0
  87. package/dist/src/registry/hosts.js +73 -0
  88. package/dist/src/registry/hosts.js.map +1 -0
  89. package/dist/src/registry/launch.d.ts +3 -0
  90. package/dist/src/registry/launch.js +29 -0
  91. package/dist/src/registry/launch.js.map +1 -0
  92. package/dist/src/registry/native-state.d.ts +20 -0
  93. package/dist/src/registry/native-state.js +127 -0
  94. package/dist/src/registry/native-state.js.map +1 -0
  95. package/dist/src/registry/sessions.d.ts +21 -3
  96. package/dist/src/registry/sessions.js +178 -37
  97. package/dist/src/registry/sessions.js.map +1 -1
  98. package/dist/src/restore/ensure.d.ts +12 -2
  99. package/dist/src/restore/ensure.js +171 -37
  100. package/dist/src/restore/ensure.js.map +1 -1
  101. package/dist/src/restore/lock.d.ts +3 -5
  102. package/dist/src/restore/lock.js +82 -52
  103. package/dist/src/restore/lock.js.map +1 -1
  104. package/dist/src/restore/status.d.ts +9 -0
  105. package/dist/src/restore/status.js +16 -0
  106. package/dist/src/restore/status.js.map +1 -0
  107. package/dist/src/snapshot/identity.d.ts +7 -0
  108. package/dist/src/snapshot/identity.js +20 -0
  109. package/dist/src/snapshot/identity.js.map +1 -0
  110. package/dist/src/snapshot/lifecycle.d.ts +9 -0
  111. package/dist/src/snapshot/lifecycle.js +37 -0
  112. package/dist/src/snapshot/lifecycle.js.map +1 -0
  113. package/dist/src/snapshot/store.d.ts +11 -1
  114. package/dist/src/snapshot/store.js +28 -9
  115. package/dist/src/snapshot/store.js.map +1 -1
  116. package/dist/src/snapshot/take.d.ts +2 -1
  117. package/dist/src/snapshot/take.js +29 -25
  118. package/dist/src/snapshot/take.js.map +1 -1
  119. package/dist/src/tmux/client.d.ts +6 -2
  120. package/dist/src/tmux/client.js +49 -25
  121. package/dist/src/tmux/client.js.map +1 -1
  122. package/dist/src/tmux/connections.d.ts +22 -0
  123. package/dist/src/tmux/connections.js +70 -0
  124. package/dist/src/tmux/connections.js.map +1 -0
  125. package/dist/src/tmux/fields.d.ts +7 -0
  126. package/dist/src/tmux/fields.js +23 -0
  127. package/dist/src/tmux/fields.js.map +1 -0
  128. package/dist/src/tmux/install.js +31 -18
  129. package/dist/src/tmux/install.js.map +1 -1
  130. package/dist/src/tools.d.ts +3 -1
  131. package/dist/src/tools.js +23 -11
  132. package/dist/src/tools.js.map +1 -1
  133. package/package.json +1 -1
  134. package/dist/src/agents/name-key.d.ts +0 -1
  135. package/dist/src/agents/name-key.js +0 -66
  136. package/dist/src/agents/name-key.js.map +0 -1
@@ -1,6 +1,7 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { homedir } from 'node:os';
1
+ import { agentRoot } from './roots.js';
2
+ import { existsSync, readFileSync, rmSync } from 'node:fs';
3
+ import { writeConfigFile } from '../config/write.js';
4
+ import { join } from 'node:path';
4
5
  import { BerthError, ExitCode } from '../errors.js';
5
6
  /**
6
7
  * Where and how each agent reads its hooks (spec §8). Every format marks
@@ -8,7 +9,6 @@ import { BerthError, ExitCode } from '../errors.js';
8
9
  * only Berth's own entries.
9
10
  */
10
11
  export const MARKER = 'BERTH_HOOK=1';
11
- const home = (env) => env.HOME || homedir();
12
12
  function readJson(file) {
13
13
  if (!existsSync(file))
14
14
  return {};
@@ -19,11 +19,7 @@ function readJson(file) {
19
19
  throw new BerthError(`cannot parse ${file}: ${error.message}`);
20
20
  }
21
21
  }
22
- function writeText(file, text) {
23
- mkdirSync(dirname(file), { recursive: true });
24
- writeFileSync(file, text);
25
- }
26
- const writeJson = (file, data) => writeText(file, `${JSON.stringify(data, null, 2)}\n`);
22
+ const writeJson = (file, data) => writeConfigFile(file, `${JSON.stringify(data, null, 2)}\n`);
27
23
  function withoutBerthNested(groups = []) {
28
24
  return groups
29
25
  .map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !h.command?.includes(MARKER)) }))
@@ -98,58 +94,83 @@ function stripTomlBlock(text) {
98
94
  */
99
95
  const tomlConfig = {
100
96
  file(env) {
101
- if (env.KIMI_CODE_HOME)
102
- return join(env.KIMI_CODE_HOME, 'config.toml');
103
- const legacy = join(home(env), '.kimi');
104
- const current = join(home(env), '.kimi-code');
105
- return existsSync(legacy) && !existsSync(current) ? join(legacy, 'config.toml') : join(current, 'config.toml');
97
+ return join(agentRoot('kimi', env), 'config.toml');
106
98
  },
107
99
  install(path, events, commandFor) {
108
100
  const base = stripTomlBlock(existsSync(path) ? readFileSync(path, 'utf8') : '');
109
101
  const tables = events.map((event) => `[[hooks]]\nevent = ${JSON.stringify(event)}\ncommand = ${JSON.stringify(commandFor(event))}\n`);
110
102
  const sep = base && !base.endsWith('\n') ? '\n\n' : base ? '\n' : '';
111
- writeText(path, `${base}${sep}${TOML_BEGIN}\n${tables.join('\n')}${TOML_END}\n`);
103
+ writeConfigFile(path, `${base}${sep}${TOML_BEGIN}\n${tables.join('\n')}${TOML_END}\n`);
112
104
  },
113
105
  uninstall(path) {
114
106
  if (existsSync(path))
115
- writeText(path, stripTomlBlock(readFileSync(path, 'utf8')));
107
+ writeConfigFile(path, stripTomlBlock(readFileSync(path, 'utf8')));
116
108
  },
117
109
  };
118
110
  // ---- OpenCode: a plugin module ------------------------------------------
119
111
  /** OpenCode loads every module in its plugins folder; this one forwards session events to `berth hook`. */
120
- export function opencodePlugin(node, cli, events) {
112
+ export function opencodePlugin(node, cli, events, electron = process.env.ELECTRON_RUN_AS_NODE === '1') {
121
113
  return `// ${MARKER}: written by \`berth hooks install opencode\`; remove with \`berth hooks uninstall opencode\`.
122
114
  import { spawn } from 'node:child_process';
123
115
 
124
116
  const EVENTS = new Set(${JSON.stringify(events)});
125
- const children = new Set();
117
+ const roots = new Set();
118
+ const failed = new Set();
119
+ let pending = Promise.resolve();
126
120
 
127
- function send(event, id) {
128
- if (!process.env.TMUX_PANE) return;
129
- try {
130
- const child = spawn(${JSON.stringify(node)}, [${JSON.stringify(cli)}, 'hook', 'opencode', event], {
131
- stdio: ['pipe', 'ignore', 'ignore'],
132
- env: { ...process.env, BERTH_HOOK: '1' },
133
- });
134
- child.on('error', () => {});
135
- child.stdin.end(JSON.stringify({ session_id: id ?? '' }));
136
- } catch {
137
- // Never disturb the agent.
138
- }
121
+ function send(event, id, payload = {}) {
122
+ if (!process.env.TMUX_PANE) return Promise.resolve();
123
+ return new Promise((resolve) => {
124
+ try {
125
+ const child = spawn(${JSON.stringify(node)}, [${JSON.stringify(cli)}, 'hook', 'opencode', event], {
126
+ stdio: ['pipe', 'ignore', 'ignore'],
127
+ env: { ...process.env, BERTH_HOOK: '1', ...${JSON.stringify(electron ? { ELECTRON_RUN_AS_NODE: '1' } : {})} },
128
+ });
129
+ const timer = setTimeout(() => child.kill('SIGKILL'), 10_000);
130
+ const finish = () => { clearTimeout(timer); resolve(); };
131
+ child.on('error', finish);
132
+ child.on('close', finish);
133
+ child.stdin.on('error', () => {});
134
+ child.stdin.end(JSON.stringify({ ...payload, session_id: id }));
135
+ } catch {
136
+ resolve();
137
+ }
138
+ });
139
139
  }
140
140
 
141
- export const BerthPlugin = async () => ({
142
- event: async ({ event }) => {
143
- if (!EVENTS.has(event.type)) return;
144
- const props = event.properties ?? event.data ?? {};
145
- const id = props.sessionID ?? props.info?.id;
146
- // Subagent sessions are children of the conversation the user resumes.
147
- if (props.info?.parentID) {
148
- if (id) children.add(id);
141
+ async function forward(event) {
142
+ if (!EVENTS.has(event.type)) return;
143
+ const props = event.properties ?? event.data ?? {};
144
+ const identity = event.type === 'session.created' || event.type === 'session.updated';
145
+ const id = props.sessionID ?? (identity ? props.info?.id : undefined);
146
+ if (typeof id !== 'string' || !id) return;
147
+ if (identity) {
148
+ if (!props.info || props.info.id !== id || props.info.parentID) {
149
+ roots.delete(id);
149
150
  return;
150
151
  }
151
- if (id && children.has(id)) return;
152
- send(event.type, id);
152
+ roots.add(id);
153
+ return send(event.type, id);
154
+ }
155
+ if (!roots.has(id)) return;
156
+ if (event.type === 'session.status') {
157
+ if (!['busy', 'retry'].includes(props.status?.type)) return;
158
+ failed.delete(id);
159
+ }
160
+ const rejected = event.type === 'permission.replied' && props.reply === 'reject';
161
+ if (event.type === 'permission.replied' && !['once', 'always', 'reject'].includes(props.reply)) return;
162
+ if (event.type === 'session.error' || rejected) failed.add(id);
163
+ const type = rejected || (event.type === 'session.idle' && failed.has(id)) ? 'session.error' : event.type;
164
+ const payload = event.type === 'permission.asked' && typeof props.permission === 'string'
165
+ ? { message: 'Approval requested: ' + props.permission } : {};
166
+ return send(type, id, payload);
167
+ }
168
+
169
+ export const BerthPlugin = async () => ({
170
+ event: ({ event }) => {
171
+ // Native publishers need not await plugin callbacks; keep child processes ordered.
172
+ pending = pending.then(() => forward(event)).catch(() => {});
173
+ return pending;
153
174
  },
154
175
  });
155
176
  `;
@@ -157,11 +178,10 @@ export const BerthPlugin = async () => ({
157
178
  function opencodeFormat(node, cli) {
158
179
  return {
159
180
  file(env) {
160
- const config = env.XDG_CONFIG_HOME || join(home(env), '.config');
161
- return join(config, 'opencode', 'plugins', 'berth.js');
181
+ return join(agentRoot('opencode', env), 'plugins', 'berth.js');
162
182
  },
163
183
  install(path, events) {
164
- writeText(path, opencodePlugin(node, cli, events));
184
+ writeConfigFile(path, opencodePlugin(node, cli, events));
165
185
  },
166
186
  uninstall(path) {
167
187
  if (existsSync(path) && readFileSync(path, 'utf8').includes(MARKER))
@@ -173,17 +193,17 @@ function opencodeFormat(node, cli) {
173
193
  export function hookFormat(target, cli = '') {
174
194
  switch (target) {
175
195
  case 'claude-settings':
176
- return nestedJson((env) => join(home(env), '.claude', 'settings.json'));
196
+ return nestedJson((env) => join(agentRoot('claude', env), 'settings.json'));
177
197
  case 'codex-hooks':
178
- return nestedJson((env) => join(home(env), '.codex', 'hooks.json'));
198
+ return nestedJson((env) => join(agentRoot('codex', env), 'hooks.json'));
179
199
  case 'gemini-settings':
180
- return nestedJson((env) => join(home(env), '.gemini', 'settings.json'));
200
+ return nestedJson((env) => join(agentRoot('gemini', env), 'settings.json'));
181
201
  case 'qoder-settings':
182
- return nestedJson((env) => join(env.QODER_CONFIG_DIR || join(home(env), '.qoder'), 'settings.json'));
202
+ return nestedJson((env) => join(agentRoot('qoder', env), 'settings.json'));
183
203
  case 'cursor-hooks':
184
- return flatJson((env) => join(home(env), '.cursor', 'hooks.json'), false);
204
+ return flatJson((env) => join(agentRoot('cursor', env), 'hooks.json'), false);
185
205
  case 'copilot-hooks':
186
- return flatJson((env) => join(env.COPILOT_HOME || join(home(env), '.copilot'), 'hooks', 'berth.json'), true);
206
+ return flatJson((env) => join(agentRoot('copilot', env), 'hooks', 'berth.json'), true);
187
207
  case 'kimi-config':
188
208
  return tomlConfig;
189
209
  case 'opencode-plugin':
@@ -192,8 +212,4 @@ export function hookFormat(target, cli = '') {
192
212
  throw new BerthError(`unknown hook target ${String(target)}`, ExitCode.usage);
193
213
  }
194
214
  }
195
- /** Whether the target file holds Berth's entries. */
196
- export function containsBerth(file) {
197
- return existsSync(file) && readFileSync(file, 'utf8').includes(MARKER);
198
- }
199
215
  //# sourceMappingURL=hook-files.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hook-files.js","sourceRoot":"","sources":["../../../src/agents/hook-files.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hook-files.js","sourceRoot":"","sources":["../../../src/agents/hook-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Adapter } from './registry.js';
2
+ export type FileHookHealth = 'installed' | 'missing' | 'broken';
3
+ /** Configuration health only; agent version and runtime delivery need separate evidence. */
4
+ export declare function inspectHooks(adapter: Adapter, env: NodeJS.ProcessEnv): FileHookHealth;
@@ -0,0 +1,95 @@
1
+ import { accessSync, constants, existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { isAbsolute } from 'node:path';
3
+ import { parse as parseToml } from 'smol-toml';
4
+ import { shellQuote, shellSplit } from '../shell.js';
5
+ import { hookFormat, MARKER, opencodePlugin } from './hook-files.js';
6
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
7
+ function accessible(file, mode) {
8
+ try {
9
+ if (!isAbsolute(file) || !statSync(file).isFile())
10
+ return false;
11
+ accessSync(file, mode);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ function targetsExist(runtime, cli) {
19
+ return accessible(runtime, constants.X_OK) && accessible(cli, constants.R_OK);
20
+ }
21
+ /** Accept only the shell command shape Berth writes; never execute it to inspect it. */
22
+ function validCommand(value, agent, event) {
23
+ if (typeof value !== 'string' || !value.startsWith(`${MARKER} `))
24
+ return false;
25
+ const words = shellSplit(value);
26
+ if (words[0] !== MARKER)
27
+ return false;
28
+ const electron = words[1] === 'ELECTRON_RUN_AS_NODE=1';
29
+ const offset = electron ? 2 : 1;
30
+ const args = words.slice(offset);
31
+ if (args.length !== 5 || args[2] !== 'hook' || args[3] !== agent || args[4] !== event)
32
+ return false;
33
+ const canonical = `${MARKER} ${electron ? 'ELECTRON_RUN_AS_NODE=1 ' : ''}${shellQuote(args[0])} ${shellQuote(args[1])} hook ${agent} ${shellQuote(event)}`;
34
+ return value === canonical && targetsExist(args[0], args[1]);
35
+ }
36
+ function jsonComplete(text, adapter, events) {
37
+ const data = record(JSON.parse(text));
38
+ if (data.disableAllHooks === true)
39
+ return false;
40
+ const flat = adapter.hookTarget === 'cursor-hooks' || adapter.hookTarget === 'copilot-hooks';
41
+ if (flat && data.version !== 1)
42
+ return false;
43
+ const hooks = record(data.hooks);
44
+ return events.every((event) => {
45
+ const groups = hooks[event];
46
+ if (!Array.isArray(groups))
47
+ return false;
48
+ const commands = flat ? groups : groups.flatMap((value) => {
49
+ const group = record(value);
50
+ if (group.matcher && group.matcher !== '*')
51
+ return [];
52
+ return Array.isArray(group.hooks) ? group.hooks : [];
53
+ });
54
+ return commands.some((value) => {
55
+ const entry = record(value);
56
+ return entry.type === 'command' && validCommand(entry.command, adapter.id, event);
57
+ });
58
+ });
59
+ }
60
+ function tomlComplete(text, adapter, events) {
61
+ const hooks = parseToml(text).hooks;
62
+ return Array.isArray(hooks) && events.every((event) => hooks.some((value) => {
63
+ const entry = record(value);
64
+ return entry.event === event && validCommand(entry.command, adapter.id, event);
65
+ }));
66
+ }
67
+ function pluginComplete(text, events) {
68
+ const match = /const child = spawn\(("(?:[^"\\]|\\.)*"), \[("(?:[^"\\]|\\.)*"), 'hook', 'opencode', event\]/.exec(text);
69
+ if (!match)
70
+ return false;
71
+ const runtime = JSON.parse(match[1]), cli = JSON.parse(match[2]);
72
+ return targetsExist(runtime, cli) && [false, true].some((electron) => text === opencodePlugin(runtime, cli, events, electron));
73
+ }
74
+ /** Configuration health only; agent version and runtime delivery need separate evidence. */
75
+ export function inspectHooks(adapter, env) {
76
+ if (!adapter.hookTarget)
77
+ return 'missing';
78
+ const file = hookFormat(adapter.hookTarget).file(env);
79
+ try {
80
+ if (!existsSync(file))
81
+ return 'missing';
82
+ const text = readFileSync(file, 'utf8');
83
+ if (!text.includes(MARKER))
84
+ return 'missing';
85
+ const events = [...new Set(Object.values(adapter.events).flat())];
86
+ const complete = adapter.hookTarget === 'opencode-plugin' ? pluginComplete(text, events)
87
+ : adapter.hookTarget === 'kimi-config' ? tomlComplete(text, adapter, events)
88
+ : jsonComplete(text, adapter, events);
89
+ return complete ? 'installed' : 'broken';
90
+ }
91
+ catch {
92
+ return 'broken';
93
+ }
94
+ }
95
+ //# sourceMappingURL=hook-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-health.js","sourceRoot":"","sources":["../../../src/agents/hook-health.ts"],"names":[],"mappings":"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"}
@@ -4,9 +4,12 @@ export declare function hookCommand(cliPath: string, agent: string, event: strin
4
4
  export declare function installHooks(agentId: string, cliPath: string, env?: NodeJS.ProcessEnv): string;
5
5
  export declare function uninstallHooks(agentId: string, env?: NodeJS.ProcessEnv): string;
6
6
  export declare function hooksInstalled(agentId: string, env?: NodeJS.ProcessEnv): boolean;
7
- export type HookStatus = 'installed' | 'missing' | 'none';
7
+ export type HookStatus = 'installed' | 'missing' | 'broken' | 'none' | 'unknown';
8
8
  /** Whether an agent's hooks are installed; `none` when Berth has no hooks for it. */
9
9
  export declare function hookStatus(agentId: string, env?: NodeJS.ProcessEnv): HookStatus;
10
+ /** Agent-owned activation that writing a hook file cannot complete. */
11
+ export declare function hookSetupGuidance(agentId: string): string | undefined;
12
+ export declare function hookInstallNotice(agentId: string, file: string): string;
10
13
  /** What a user is told when an agent in use has no hooks (spec §4.2). */
11
14
  export declare function hookHint(agentId: string): string;
12
15
  /** What a hook event changes on the session, or nothing when the event does not matter. */
@@ -1,15 +1,22 @@
1
+ import { copilotCursor } from './copilot-state.js';
2
+ import { lateBindingId } from './late-binding.js';
3
+ import { geminiCursor } from './gemini-state.js';
4
+ import { processLifetime } from '../process-owner.js';
5
+ import { captureProfile, profileKey, readProfile } from './profile.js';
6
+ import { withLock } from '../restore/lock.js';
1
7
  import { BerthError, ExitCode } from '../errors.js';
2
8
  import { shellQuote } from '../shell.js';
3
9
  import { Tmux } from '../tmux/client.js';
4
10
  import { presentState } from '../present/state.js';
5
11
  import { createContext } from '../context.js';
12
+ import { discover, processes, sessionProducers, PRODUCER_CHANGED, BINDING_UNVERIFIED } from '../registry/discover.js';
6
13
  import { snapshotQuietly } from '../snapshot/take.js';
7
14
  import { requireAdapter } from './commands.js';
8
15
  import { getAdapter, stateForEvent } from './registry.js';
9
- import { containsBerth, hookFormat, MARKER } from './hook-files.js';
16
+ import { inspectHooks } from './hook-health.js';
17
+ import { hookFormat, MARKER } from './hook-files.js';
10
18
  function hookEvents(adapter) {
11
- const { start, working, waiting, done } = adapter.events;
12
- return [...new Set([...start, ...working, ...waiting, ...done])];
19
+ return [...new Set(Object.values(adapter.events).flat())];
13
20
  }
14
21
  function targetOf(adapter) {
15
22
  if (!adapter.hookTarget)
@@ -18,13 +25,16 @@ function targetOf(adapter) {
18
25
  }
19
26
  /** Command an agent runs for an event: `BERTH_HOOK=1 <node> <cli> hook <agent> <event>`. */
20
27
  export function hookCommand(cliPath, agent, event) {
21
- return `${MARKER} ${shellQuote(process.execPath)} ${shellQuote(cliPath)} hook ${agent} ${shellQuote(event)}`;
28
+ const runtimeMode = process.env.ELECTRON_RUN_AS_NODE === '1' ? 'ELECTRON_RUN_AS_NODE=1 ' : '';
29
+ return `${MARKER} ${runtimeMode}${shellQuote(process.execPath)} ${shellQuote(cliPath)} hook ${agent} ${shellQuote(event)}`;
22
30
  }
23
31
  export function installHooks(agentId, cliPath, env = process.env) {
24
32
  const adapter = requireAdapter(agentId);
25
33
  const format = hookFormat(targetOf(adapter), cliPath);
26
34
  const file = format.file(env);
27
35
  format.install(file, hookEvents(adapter), (event) => hookCommand(cliPath, adapter.id, event), adapter);
36
+ if (inspectHooks(adapter, env) !== 'installed')
37
+ throw new BerthError(`Hooks were written to ${file}, but configuration remains incomplete, disabled, or its runtime is unavailable. Inspect this file before retrying.`);
28
38
  return file;
29
39
  }
30
40
  export function uninstallHooks(agentId, env = process.env) {
@@ -37,7 +47,7 @@ export function hooksInstalled(agentId, env = process.env) {
37
47
  const adapter = requireAdapter(agentId);
38
48
  if (!adapter.hookTarget)
39
49
  return false;
40
- return containsBerth(hookFormat(adapter.hookTarget).file(env));
50
+ return inspectHooks(adapter, env) === 'installed';
41
51
  }
42
52
  /** Whether an agent's hooks are installed; `none` when Berth has no hooks for it. */
43
53
  export function hookStatus(agentId, env = process.env) {
@@ -45,16 +55,26 @@ export function hookStatus(agentId, env = process.env) {
45
55
  if (!adapter?.hookTarget)
46
56
  return 'none';
47
57
  try {
48
- return hooksInstalled(agentId, env) ? 'installed' : 'missing';
58
+ return inspectHooks(adapter, env);
49
59
  }
50
60
  catch {
51
61
  return 'missing';
52
62
  }
53
63
  }
64
+ /** Agent-owned activation that writing a hook file cannot complete. */
65
+ export function hookSetupGuidance(agentId) {
66
+ return agentId === 'codex'
67
+ ? 'Codex manages hook trust in /hooks. Review new or changed Berth hooks there before they can run.'
68
+ : undefined;
69
+ }
70
+ export function hookInstallNotice(agentId, file) {
71
+ const name = getAdapter(agentId)?.displayName ?? agentId;
72
+ return `Berth: ${name} hook configuration installed (${file}).${hookSetupGuidance(agentId) ? ` ${hookSetupGuidance(agentId)}` : ''}`;
73
+ }
54
74
  /** What a user is told when an agent in use has no hooks (spec §4.2). */
55
75
  export function hookHint(agentId) {
56
76
  const name = getAdapter(agentId)?.displayName ?? agentId;
57
- return `Berth: ${name} hooks are not installed, so after a restart this session starts a new conversation. Run: berth hooks install ${agentId}`;
77
+ return `Berth: ${name} hooks are not installed. State updates and future conversation binding may be unavailable. Run: berth hooks install ${agentId}`;
58
78
  }
59
79
  /** What a hook event changes on the session, or nothing when the event does not matter. */
60
80
  export function metaForEvent(adapter, event, payload) {
@@ -64,13 +84,33 @@ export function metaForEvent(adapter, event, payload) {
64
84
  meta.conversation = id;
65
85
  let state = stateForEvent(adapter, event);
66
86
  // Some agents also notify for idle reminders; only a permission request means waiting.
67
- if (state === 'waiting' && adapter.waitingWhen && !adapter.waitingWhen(payload))
87
+ if (state === 'waiting' && adapter.waitingWhen && !adapter.waitingWhen(payload, event))
68
88
  state = undefined;
69
- if (state)
89
+ if (state !== undefined && adapter.idleWhen?.(event, payload))
90
+ state = '';
91
+ if (state !== undefined)
70
92
  meta.state = state;
71
- // Latest notification text, shown in session lists (spec §6).
72
- if (state && typeof payload.message === 'string' && payload.message.trim())
73
- meta.note = payload.message.trim().slice(0, 200);
93
+ // Latest notification text, shown in session lists (spec §6). A new state
94
+ // replaces it, so a finished session never shows an old permission request.
95
+ if (state !== undefined) {
96
+ const input = payload.tool_input;
97
+ const description = input && typeof input === 'object' && 'description' in input ? input.description : undefined;
98
+ const message = typeof payload.message === 'string' && payload.message.trim() ? payload.message
99
+ : state === 'waiting' && typeof description === 'string' ? description
100
+ : state === 'waiting' && typeof payload.tool_name === 'string' ? `Approval requested: ${payload.tool_name}` : '';
101
+ meta.note = state === '' ? '' : message.trim().slice(0, 200);
102
+ }
103
+ // The session's subtitle: the first line of the prompt the user just sent (spec §4.2).
104
+ // Text the agent injects itself (`<task-notification>`, `<command-name>`…) is not the user's.
105
+ if (state === 'working' && typeof payload.prompt === 'string' && !/^\s*<[a-z][\w-]*[\s>]/i.test(payload.prompt)) {
106
+ // Attachment placeholders such as `[Image #3]` say nothing about the topic.
107
+ const first = payload.prompt
108
+ .split('\n')
109
+ .map((l) => l.replace(/\[(Image|Pasted text) #\d+[^\]]*\]/g, '').replace(/\s+/g, ' ').trim())
110
+ .find(Boolean);
111
+ if (first)
112
+ meta.topic = first.slice(0, 120);
113
+ }
74
114
  return meta;
75
115
  }
76
116
  /**
@@ -91,22 +131,115 @@ export function handleHook(agentId, event, stdin, env = process.env) {
91
131
  catch {
92
132
  payload = {};
93
133
  }
94
- const meta = metaForEvent(adapter, event, payload);
134
+ let meta = metaForEvent(adapter, event, payload);
135
+ const lateId = lateBindingId(agentId, event, payload);
95
136
  if (Object.keys(meta).length === 0)
96
137
  return {};
97
138
  const tmux = new Tmux({ bin: env.BERTH_TMUX || 'tmux', socketPath });
98
139
  const session = tmux.sessionOfPane(pane);
99
140
  if (!session)
100
141
  return {};
101
- tmux.setMeta(session, meta);
102
- if (meta.state)
103
- presentState(tmux, session, meta.state, { note: meta.note });
104
- // A new conversation id must reach the snapshot before any reboot.
105
- if (meta.conversation) {
106
- const ctx = createContext({ ...env, BERTH_TMUX_SOCKET_PATH: socketPath });
107
- snapshotQuietly(ctx);
108
- }
109
- return meta;
142
+ const ctx = createContext({ ...env, BERTH_TMUX_SOCKET_PATH: socketPath });
143
+ return withLock(ctx.config.stateDir, () => {
144
+ let live = tmux.listSessions().find((s) => s.name === session);
145
+ const table = processes();
146
+ const producers = sessionProducers(ctx, session, table);
147
+ const ancestors = [];
148
+ for (let pid = process.pid; pid > 1 && ancestors.length < 64 && !ancestors.includes(pid);) {
149
+ ancestors.push(pid);
150
+ pid = table.find((p) => p.pid === pid)?.ppid ?? 0;
151
+ }
152
+ if (!live?.meta.agent || live.meta.agent === 'shell') {
153
+ const producer = producers[0];
154
+ if (!ctx.config.autoAdopt || (!adapter.events.start.includes(event) && !lateId) || producers.length !== 1
155
+ || producer.agent !== agentId || producer.pane !== pane || !ancestors.includes(producer.pid))
156
+ return {};
157
+ discover(ctx);
158
+ live = tmux.listSessions().find((s) => s.name === session);
159
+ }
160
+ if (!live?.meta.agent)
161
+ return {};
162
+ if (agentId === 'gemini' && event === 'BeforeModel' && live.meta.state !== 'waiting')
163
+ return {};
164
+ if (live.meta.agent !== agentId) {
165
+ tmux.setMeta(session, { bindingIssue: 'Agent producer changed. Verify the conversation before restoring.' });
166
+ snapshotQuietly(ctx);
167
+ return {};
168
+ }
169
+ const otherPane = live.meta.producerPane && live.meta.producerPane !== pane;
170
+ if (producers.length > 1 || otherPane) {
171
+ tmux.setMeta(session, { bindingIssue: 'Multiple independent agents. Move each agent to its own tmux session before restoring.' });
172
+ snapshotQuietly(ctx);
173
+ return {};
174
+ }
175
+ const producer = producers[0];
176
+ if (producer) {
177
+ if (producer.agent !== agentId || producer.pane !== pane || !ancestors.includes(producer.pid))
178
+ return {};
179
+ }
180
+ if (!producer && (!live.meta.recovery || live.meta.recovery === 'confirmed') && meta.conversation && live.meta.conversation && meta.conversation !== live.meta.conversation) {
181
+ tmux.setMeta(session, { bindingIssue: 'Cannot verify the producer of a different conversation. Inspect the agent and re-adopt before restoring.' });
182
+ snapshotQuietly(ctx);
183
+ return {};
184
+ }
185
+ const payloadId = payload.session_id ?? payload.sessionId ?? payload.sessionID ?? payload.conversation_id;
186
+ if (!adapter.events.start.includes(event) && typeof payloadId === 'string' && live.meta.conversation && payloadId !== live.meta.conversation)
187
+ return {};
188
+ if (producer && lateId && !live.meta.conversation && (!live.meta.recovery || live.meta.recovery === 'confirmed')
189
+ && (!live.meta.bindingIssue || [PRODUCER_CHANGED, BINDING_UNVERIFIED].includes(live.meta.bindingIssue))) {
190
+ const existingProfile = readProfile(agentId, live.meta.profile);
191
+ const observedProfile = captureProfile(agentId, env);
192
+ if (live.meta.profile && (!existingProfile || profileKey(agentId, existingProfile) !== profileKey(agentId, observedProfile)))
193
+ return {};
194
+ meta = { ...meta, conversation: lateId };
195
+ }
196
+ // Claim once in tmux, so simultaneous first hooks cannot both own the row.
197
+ tmux.run(['set-option', '-o', '-t', `=${session}:`, '@berth_producerPane', pane], true);
198
+ const owner = tmux.run(['display-message', '-p', '-t', `=${session}:`, '#{@berth_producerPane}'], true).stdout.trim();
199
+ if (owner !== pane) {
200
+ tmux.setMeta(session, { bindingIssue: 'Another pane owns this conversation. Separate agents before restoring.' });
201
+ snapshotQuietly(ctx);
202
+ return {};
203
+ }
204
+ const confirmsExisting = !!producer && typeof payloadId === 'string' && !!live.meta.conversation && payloadId === live.meta.conversation;
205
+ const profile = meta.conversation || (confirmsExisting && !live.meta.profile) ? captureProfile(agentId, env) : undefined;
206
+ if (live.meta.recovery && live.meta.recovery !== 'confirmed' && meta.conversation) {
207
+ const expectedProfile = readProfile(agentId, live.meta.profile);
208
+ if (profile && expectedProfile && profileKey(agentId, profile) !== profileKey(agentId, expectedProfile)) {
209
+ tmux.setMeta(session, { recovery: 'failed', recoveryDetail: 'Agent opened a different profile. The expected conversation and profile were preserved.' });
210
+ snapshotQuietly(ctx);
211
+ return {};
212
+ }
213
+ if (meta.conversation !== live.meta.conversation) {
214
+ tmux.setMeta(session, { recovery: 'failed', recoveryDetail: 'Agent opened a different conversation. Inspect the terminal; the expected ID was preserved.' });
215
+ snapshotQuietly(ctx);
216
+ return {};
217
+ }
218
+ const dead = tmux.run(['display-message', '-p', '-t', pane, '#{pane_dead}'], true).stdout.trim();
219
+ if (dead === '1')
220
+ return {};
221
+ tmux.setMeta(session, { recovery: 'confirmed', recoveryDetail: '' });
222
+ tmux.run(['set-option', '-w', '-t', `=${session}:`, 'remain-on-exit', 'off']);
223
+ }
224
+ tmux.setMeta(session, {
225
+ ...meta, producerPane: pane,
226
+ ...(agentId === 'gemini' && event === 'BeforeAgent' ? {
227
+ nativeTranscript: producer ? geminiCursor(payload.transcript_path, meta.conversation || live.meta.conversation || '', env) : '',
228
+ } : agentId === 'copilot' && event === 'userPromptSubmitted' ? {
229
+ nativeTranscript: producer ? copilotCursor(meta.conversation || live.meta.conversation || '', env) : '',
230
+ } : ['gemini', 'copilot'].includes(agentId) && adapter.events.start.includes(event) ? { nativeTranscript: '' } : {}),
231
+ ...(producer ? { producerLifetime: processLifetime(producer.pid)?.key } : {}),
232
+ ...(producer && (meta.conversation || (typeof payloadId === 'string' && payloadId === live.meta.conversation)) && (live.meta.bindingIssue === PRODUCER_CHANGED || live.meta.bindingIssue === BINDING_UNVERIFIED) ? { bindingIssue: '' } : {}),
233
+ ...(profile ? { profile: JSON.stringify(profile) } : {}),
234
+ });
235
+ if (meta.state !== undefined)
236
+ presentState(tmux, session, meta.state, { note: meta.note });
237
+ // A new conversation id must reach the snapshot before any reboot.
238
+ if (meta.conversation || (confirmsExisting && (profile || live.meta.bindingIssue === PRODUCER_CHANGED || live.meta.bindingIssue === BINDING_UNVERIFIED))) {
239
+ snapshotQuietly(ctx);
240
+ }
241
+ return meta;
242
+ });
110
243
  }
111
244
  catch {
112
245
  return {};