@chrisromp/copilot-bridge 0.8.4 → 0.9.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.
@@ -0,0 +1,302 @@
1
+ /**
2
+ * hooks-loader.ts — Discover and load session hooks from plugins, user config, and workspace.
3
+ *
4
+ * Uses the official CLI hooks.json format:
5
+ * {
6
+ * "version": 1,
7
+ * "hooks": {
8
+ * "preToolUse": [
9
+ * { "type": "command", "bash": "./scripts/guard.sh", "cwd": ".", "timeoutSec": 10 }
10
+ * ]
11
+ * }
12
+ * }
13
+ *
14
+ * Hooks are shell commands. Input is piped as JSON to stdin, output read from stdout.
15
+ * Multiple hooks per type run in sequence; for preToolUse, first "deny" wins.
16
+ *
17
+ * Discovery order (lowest → highest priority, later entries append):
18
+ * 1. Plugin hooks: ~/.copilot/installed-plugins/.../hooks.json
19
+ * 2. User hooks: ~/.copilot/hooks.json
20
+ * 3. Workspace hooks: <workspace>/.github/hooks/hooks.json, <workspace>/.github/hooks.json, <workspace>/hooks.json
21
+ */
22
+ import * as fs from 'node:fs';
23
+ import * as path from 'node:path';
24
+ import { spawn } from 'node:child_process';
25
+ import { createLogger } from '../logger.js';
26
+ const log = createLogger('hooks');
27
+ /** CLI hook type names → SDK SessionHooks keys */
28
+ const HOOK_TYPE_MAP = {
29
+ preToolUse: 'onPreToolUse',
30
+ postToolUse: 'onPostToolUse',
31
+ userPromptSubmitted: 'onUserPromptSubmitted',
32
+ sessionStart: 'onSessionStart',
33
+ sessionEnd: 'onSessionEnd',
34
+ errorOccurred: 'onErrorOccurred',
35
+ };
36
+ const VALID_HOOK_TYPES = new Set(Object.keys(HOOK_TYPE_MAP));
37
+ /**
38
+ * Discover all hooks.json files in priority order (lowest first).
39
+ */
40
+ function discoverHooksFiles(workingDirectory, options) {
41
+ const home = process.env.HOME;
42
+ const results = [];
43
+ // 1. Plugin hooks (lowest priority)
44
+ if (home) {
45
+ const pluginsDir = path.join(home, '.copilot', 'installed-plugins');
46
+ if (fs.existsSync(pluginsDir)) {
47
+ const walk = (dir, depth) => {
48
+ if (depth > 3)
49
+ return;
50
+ try {
51
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
52
+ const full = path.join(dir, entry.name);
53
+ if (entry.isFile() && entry.name === 'hooks.json') {
54
+ results.push({ file: full, baseDir: dir });
55
+ }
56
+ else if (entry.isDirectory()) {
57
+ walk(full, depth + 1);
58
+ }
59
+ }
60
+ }
61
+ catch { /* permission errors */ }
62
+ };
63
+ walk(pluginsDir, 0);
64
+ }
65
+ }
66
+ // 2. User hooks
67
+ if (home) {
68
+ const userHooks = path.join(home, '.copilot', 'hooks.json');
69
+ if (fs.existsSync(userHooks)) {
70
+ results.push({ file: userHooks, baseDir: path.join(home, '.copilot') });
71
+ }
72
+ }
73
+ // 3. Workspace hooks (only if explicitly allowed — executes arbitrary code)
74
+ if (options?.allowWorkspaceHooks) {
75
+ const wsGithubHooks = path.join(workingDirectory, '.github', 'hooks', 'hooks.json');
76
+ if (fs.existsSync(wsGithubHooks)) {
77
+ results.push({ file: wsGithubHooks, baseDir: path.join(workingDirectory, '.github', 'hooks') });
78
+ }
79
+ const wsGithub = path.join(workingDirectory, '.github', 'hooks.json');
80
+ if (fs.existsSync(wsGithub)) {
81
+ results.push({ file: wsGithub, baseDir: path.join(workingDirectory, '.github') });
82
+ }
83
+ const wsRoot = path.join(workingDirectory, 'hooks.json');
84
+ if (fs.existsSync(wsRoot)) {
85
+ results.push({ file: wsRoot, baseDir: workingDirectory });
86
+ }
87
+ }
88
+ return results;
89
+ }
90
+ /**
91
+ * Parse a hooks.json file and return validated hook commands per type.
92
+ */
93
+ function parseHooksConfig(filePath) {
94
+ const result = new Map();
95
+ try {
96
+ const raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
97
+ const hooks = raw.hooks;
98
+ if (typeof hooks !== 'object' || hooks === null) {
99
+ log.warn(`Invalid hooks.json format (missing "hooks" key): ${filePath}`);
100
+ return result;
101
+ }
102
+ for (const [hookType, commands] of Object.entries(hooks)) {
103
+ if (!VALID_HOOK_TYPES.has(hookType)) {
104
+ log.warn(`Unknown hook type "${hookType}" in ${filePath}, skipping`);
105
+ continue;
106
+ }
107
+ if (!Array.isArray(commands)) {
108
+ log.warn(`Hook type "${hookType}" must be an array in ${filePath}, skipping`);
109
+ continue;
110
+ }
111
+ const valid = [];
112
+ for (const cmd of commands) {
113
+ if (!cmd || typeof cmd !== 'object' || cmd.type !== 'command' || (!cmd.bash && !cmd.powershell)) {
114
+ log.warn(`Invalid hook command for "${hookType}" in ${filePath}, skipping`);
115
+ continue;
116
+ }
117
+ valid.push(cmd);
118
+ }
119
+ if (valid.length > 0) {
120
+ result.set(hookType, valid);
121
+ }
122
+ }
123
+ }
124
+ catch (err) {
125
+ log.warn(`Failed to parse ${filePath}: ${err}`);
126
+ }
127
+ return result;
128
+ }
129
+ /**
130
+ * Execute a hook command by spawning a shell process (async, non-blocking).
131
+ * Input is piped as JSON to stdin, output parsed from stdout.
132
+ */
133
+ async function executeHookCommand(cmd, input, baseDir) {
134
+ const shell = cmd.bash ? 'bash' : 'powershell';
135
+ const script = cmd.bash ?? cmd.powershell;
136
+ const cwd = cmd.cwd ? path.resolve(baseDir, cmd.cwd) : baseDir;
137
+ const timeoutMs = (cmd.timeoutSec ?? 30) * 1000;
138
+ return new Promise((resolve) => {
139
+ let resolved = false;
140
+ const done = (value) => {
141
+ if (resolved)
142
+ return;
143
+ resolved = true;
144
+ clearTimeout(timer);
145
+ resolve(value);
146
+ };
147
+ const child = spawn(shell, ['-c', script], {
148
+ cwd,
149
+ env: { ...process.env, ...cmd.env },
150
+ stdio: ['pipe', 'pipe', 'pipe'],
151
+ });
152
+ const timer = setTimeout(() => {
153
+ log.warn(`Hook command timed out after ${cmd.timeoutSec ?? 30}s: ${script}`);
154
+ child.kill('SIGKILL');
155
+ done(undefined);
156
+ }, timeoutMs);
157
+ let stdout = '';
158
+ let stderr = '';
159
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
160
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
161
+ child.on('close', (code, signal) => {
162
+ if (signal) {
163
+ done(undefined);
164
+ return;
165
+ }
166
+ if (code !== 0) {
167
+ log.warn(`Hook command failed (exit ${code}): ${script}${stderr ? ' — ' + stderr.trim() : ''}`);
168
+ done(undefined);
169
+ return;
170
+ }
171
+ const trimmed = stdout.trim();
172
+ if (!trimmed) {
173
+ done(undefined);
174
+ return;
175
+ }
176
+ try {
177
+ done(JSON.parse(trimmed));
178
+ }
179
+ catch {
180
+ log.warn(`Hook command returned invalid JSON: ${script}`);
181
+ done(undefined);
182
+ }
183
+ });
184
+ child.on('error', (err) => {
185
+ log.warn(`Hook command failed: ${script} — ${err.message}`);
186
+ done(undefined);
187
+ });
188
+ child.stdin.write(JSON.stringify(input));
189
+ child.stdin.end();
190
+ });
191
+ }
192
+ /**
193
+ * Build a SessionHooks callback that runs all commands for a given hook type.
194
+ * For preToolUse: deny > ask > allow precedence. First "deny" or "ask" short-circuits.
195
+ */
196
+ function buildHookCallback(hookType, allCommands) {
197
+ return async (input, _invocation) => {
198
+ log.debug(`Hook callback invoked: ${hookType} (${allCommands.length} command(s)), tool=${input.toolName ?? 'n/a'}`);
199
+ let mergedResult = undefined;
200
+ for (const { cmd, baseDir } of allCommands) {
201
+ const result = await executeHookCommand(cmd, input, baseDir);
202
+ if (!result)
203
+ continue;
204
+ if (!mergedResult) {
205
+ mergedResult = result;
206
+ }
207
+ else {
208
+ Object.assign(mergedResult, result);
209
+ }
210
+ // For preToolUse, deny and ask short-circuit (deny > ask > allow)
211
+ if (hookType === 'preToolUse') {
212
+ if (result.permissionDecision === 'deny' || result.permissionDecision === 'ask') {
213
+ return mergedResult;
214
+ }
215
+ }
216
+ }
217
+ return mergedResult;
218
+ };
219
+ }
220
+ /**
221
+ * Discover and load all hooks for a given workspace.
222
+ * Returns a SessionHooks object ready to pass to the SDK, or undefined if no hooks found.
223
+ */
224
+ export async function loadHooks(workingDirectory, options) {
225
+ const files = discoverHooksFiles(workingDirectory, options);
226
+ if (files.length === 0)
227
+ return undefined;
228
+ // Collect all commands per hook type across all files (all sources append)
229
+ const commandsByType = new Map();
230
+ for (const { file, baseDir } of files) {
231
+ const config = parseHooksConfig(file);
232
+ for (const [hookType, commands] of config) {
233
+ const existing = commandsByType.get(hookType) ?? [];
234
+ for (const cmd of commands) {
235
+ existing.push({ cmd, baseDir });
236
+ }
237
+ commandsByType.set(hookType, existing);
238
+ }
239
+ log.debug(`Loaded hooks config from ${file} (${config.size} hook type(s))`);
240
+ }
241
+ if (commandsByType.size === 0)
242
+ return undefined;
243
+ const hooks = {};
244
+ for (const [hookType, commands] of commandsByType) {
245
+ const sdkKey = HOOK_TYPE_MAP[hookType];
246
+ if (!sdkKey)
247
+ continue;
248
+ hooks[sdkKey] = buildHookCallback(hookType, commands);
249
+ log.info(`Registered ${hookType} hook (${commands.length} command(s))`);
250
+ }
251
+ return Object.keys(hooks).length > 0 ? hooks : undefined;
252
+ }
253
+ /**
254
+ * Return metadata about configured hooks without executing them.
255
+ * Used by /tools to show which hooks are active.
256
+ */
257
+ export function getHooksInfo(workingDirectory, options) {
258
+ const files = discoverHooksFiles(workingDirectory, options);
259
+ if (files.length === 0)
260
+ return [];
261
+ const home = process.env.HOME ?? '';
262
+ // Accumulate command counts per hook type per source
263
+ const info = new Map();
264
+ for (const { file } of files) {
265
+ const config = parseHooksConfig(file);
266
+ const normalized = file.split(path.sep).join('/');
267
+ let source = 'user';
268
+ if (normalized.includes('installed-plugins'))
269
+ source = 'plugin';
270
+ else if (home && normalized.includes(home.split(path.sep).join('/') + '/.copilot/'))
271
+ source = 'user';
272
+ else
273
+ source = 'workspace';
274
+ for (const [hookType, commands] of config) {
275
+ const existing = info.get(hookType);
276
+ if (existing) {
277
+ existing.commandCount += commands.length;
278
+ // Higher-priority source wins for display
279
+ existing.source = source;
280
+ }
281
+ else {
282
+ info.set(hookType, { source, commandCount: commands.length });
283
+ }
284
+ }
285
+ }
286
+ return [...info.entries()]
287
+ .map(([hookType, { source, commandCount }]) => ({ hookType, source, commandCount }))
288
+ .sort((a, b) => a.hookType.localeCompare(b.hookType));
289
+ }
290
+ /**
291
+ * Merge two SessionHooks objects. The override hooks take precedence.
292
+ */
293
+ export function mergeHooks(base, override) {
294
+ if (!base && !override)
295
+ return undefined;
296
+ if (!base)
297
+ return override;
298
+ if (!override)
299
+ return base;
300
+ return { ...base, ...override };
301
+ }
302
+ //# sourceMappingURL=hooks-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-loader.js","sourceRoot":"","sources":["../../src/core/hooks-loader.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { CopilotBridge } from './bridge.js';
2
2
  import { type ChannelPrefs } from '../state/store.js';
3
3
  import { type InterAgentContext } from './inter-agent.js';
4
4
  import type { McpServerInfo } from './command-handler.js';
5
+ import { type HookInfo } from './hooks-loader.js';
5
6
  import type { ChannelAdapter } from '../types.js';
6
7
  /** Custom tools auto-approved without interactive prompt (they enforce workspace boundaries internally). */
7
8
  export declare const BRIDGE_CUSTOM_TOOLS: string[];
@@ -20,9 +21,17 @@ export declare class SessionManager {
20
21
  private lastMessageUserIds;
21
22
  private sessionMcpServers;
22
23
  private sessionSkillDirs;
24
+ private workspaceHooks;
23
25
  private sendFileHandler;
24
26
  private getAdapterForChannel;
25
27
  constructor(bridge: CopilotBridge);
28
+ /** Resolve hooks for a workspace, caching the result. */
29
+ private resolveHooks;
30
+ /**
31
+ * Wrap loaded hooks so that preToolUse "ask" decisions trigger the bridge's
32
+ * interactive permission prompt instead of being ignored by the CLI.
33
+ */
34
+ private wrapHooksWithAsk;
26
35
  /** Register a handler for session events (streaming, tool calls, etc.) */
27
36
  onSessionEvent(handler: SessionEventHandler): void;
28
37
  /** Register handler for the send_file custom tool. */
@@ -43,6 +52,8 @@ export declare class SessionManager {
43
52
  source: string;
44
53
  pending?: boolean;
45
54
  }[];
55
+ /** Get info about configured hooks for a channel's workspace. */
56
+ getHooksInfo(channelId: string): HookInfo[];
46
57
  /** List tools the SDK CLI process has available (via server RPC). */
47
58
  listSessionTools(channelId: string): Promise<{
48
59
  name: string;
@@ -110,6 +121,8 @@ export declare class SessionManager {
110
121
  resolveUserInput(channelId: string, answer: string): boolean;
111
122
  /** Check if channel has a pending permission request. */
112
123
  hasPendingPermission(channelId: string): boolean;
124
+ /** Check if the current pending permission is from a hook (no remember allowed). */
125
+ isHookPermission(channelId: string): boolean;
113
126
  /** Get the current session ID for a channel (if any). */
114
127
  getSessionId(channelId: string): string | undefined;
115
128
  /** Abort the current turn for a channel's session. */
@@ -1 +1 @@
1
- {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/core/session-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/core/session-manager.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,7 @@ import { addJob, removeJob, pauseJob, resumeJob, listJobs, formatInTimezone } fr
9
9
  import { canCall, createContext, extendContext, getBotWorkspaceMap, buildWorkspacePrompt, buildCallerPrompt, resolveAgentDefinition, } from './inter-agent.js';
10
10
  import { createLogger } from '../logger.js';
11
11
  import { tryWithFallback, isModelError, buildFallbackChain } from './model-fallback.js';
12
+ import { loadHooks, getHooksInfo } from './hooks-loader.js';
12
13
  const log = createLogger('session');
13
14
  /** Custom tools auto-approved without interactive prompt (they enforce workspace boundaries internally). */
14
15
  export const BRIDGE_CUSTOM_TOOLS = ['send_file', 'show_file_in_chat', 'ask_agent', 'schedule'];
@@ -233,6 +234,10 @@ export function extractCommandPatterns(input) {
233
234
  * - ~/.agents/skills/ (user-level)
234
235
  * - <workspace>/.github/skills/ (project-level)
235
236
  * - <workspace>/.agents/skills/ (project-level)
237
+ * - Plugin skills from ~/.copilot/installed-plugins/ (lowest priority)
238
+ *
239
+ * Per CLI spec, skills use first-found-wins dedup by name.
240
+ * Plugin skills are appended last so user/workspace skills take precedence.
236
241
  */
237
242
  function discoverSkillDirectories(workingDirectory) {
238
243
  const home = process.env.HOME;
@@ -246,6 +251,31 @@ function discoverSkillDirectories(workingDirectory) {
246
251
  roots.push(path.join(workingDirectory, '.github', 'skills'));
247
252
  // Project-level skills (legacy)
248
253
  roots.push(path.join(workingDirectory, '.agents', 'skills'));
254
+ // Plugin skills (lowest priority — appended last so earlier sources win)
255
+ if (home) {
256
+ const pluginsDir = path.join(home, '.copilot', 'installed-plugins');
257
+ if (fs.existsSync(pluginsDir)) {
258
+ const walk = (dir, depth) => {
259
+ if (depth > 3)
260
+ return;
261
+ try {
262
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
263
+ if (!entry.isDirectory())
264
+ continue;
265
+ const full = path.join(dir, entry.name);
266
+ if (entry.name === 'skills') {
267
+ roots.push(full);
268
+ }
269
+ else {
270
+ walk(full, depth + 1);
271
+ }
272
+ }
273
+ }
274
+ catch { /* permission errors etc */ }
275
+ };
276
+ walk(pluginsDir, 0);
277
+ }
278
+ }
249
279
  const dirs = [];
250
280
  for (const skillsRoot of roots) {
251
281
  if (!fs.existsSync(skillsRoot))
@@ -282,6 +312,8 @@ export class SessionManager {
282
312
  sessionMcpServers = new Map(); // channelId → server names
283
313
  // Skill directories that were passed to the session at creation/resume time
284
314
  sessionSkillDirs = new Map(); // channelId → skill dir paths
315
+ // Loaded session hooks per workspace (cached after first load)
316
+ workspaceHooks = new Map();
285
317
  // Handler for send_file tool (set by index.ts, calls adapter.sendFile)
286
318
  sendFileHandler = null;
287
319
  getAdapterForChannel = null;
@@ -290,6 +322,74 @@ export class SessionManager {
290
322
  this.mcpServers = loadMcpServers();
291
323
  ensureWorkspacesDir();
292
324
  }
325
+ /** Resolve hooks for a workspace, caching the result. */
326
+ async resolveHooks(workingDirectory) {
327
+ const cached = this.workspaceHooks.get(workingDirectory);
328
+ if (cached !== undefined || this.workspaceHooks.has(workingDirectory))
329
+ return cached;
330
+ const allowWorkspaceHooks = getConfig().defaults.allowWorkspaceHooks ?? false;
331
+ const hooks = await loadHooks(workingDirectory, { allowWorkspaceHooks });
332
+ this.workspaceHooks.set(workingDirectory, hooks);
333
+ return hooks;
334
+ }
335
+ /**
336
+ * Wrap loaded hooks so that preToolUse "ask" decisions trigger the bridge's
337
+ * interactive permission prompt instead of being ignored by the CLI.
338
+ */
339
+ wrapHooksWithAsk(hooks, channelId) {
340
+ if (!hooks?.onPreToolUse)
341
+ return hooks;
342
+ const originalPreToolUse = hooks.onPreToolUse;
343
+ const wrappedPreToolUse = async (input, invocation) => {
344
+ const result = await originalPreToolUse(input, invocation);
345
+ if (!result || result.permissionDecision !== 'ask')
346
+ return result;
347
+ // Convert "ask" into an interactive permission prompt
348
+ const reason = result.permissionDecisionReason ?? 'Hook requires confirmation';
349
+ const toolName = input.toolName ?? 'unknown';
350
+ return new Promise((resolve) => {
351
+ const entry = {
352
+ sessionId: invocation.sessionId,
353
+ channelId,
354
+ toolName: `hook:${toolName}`,
355
+ serverName: undefined,
356
+ fromHook: true,
357
+ hookReason: reason,
358
+ toolInput: input.toolArgs,
359
+ commands: [],
360
+ resolve: (decision) => {
361
+ if (decision.kind === 'approved') {
362
+ resolve({ ...result, permissionDecision: 'allow' });
363
+ }
364
+ else {
365
+ resolve({ ...result, permissionDecision: 'deny', permissionDecisionReason: reason });
366
+ }
367
+ },
368
+ createdAt: Date.now(),
369
+ };
370
+ let queue = this.pendingPermissions.get(channelId);
371
+ if (!queue) {
372
+ queue = [];
373
+ this.pendingPermissions.set(channelId, queue);
374
+ }
375
+ queue.push(entry);
376
+ if (queue.length === 1) {
377
+ this.eventHandler?.(invocation.sessionId, channelId, {
378
+ type: 'bridge.permission_request',
379
+ data: {
380
+ toolName: `hook:${toolName}`,
381
+ serverName: undefined,
382
+ input: input.toolArgs,
383
+ commands: [],
384
+ hookReason: reason,
385
+ fromHook: true,
386
+ },
387
+ });
388
+ }
389
+ });
390
+ };
391
+ return { ...hooks, onPreToolUse: wrappedPreToolUse };
392
+ }
293
393
  /** Register a handler for session events (streaming, tool calls, etc.) */
294
394
  onSessionEvent(handler) {
295
395
  this.eventHandler = handler;
@@ -360,7 +460,9 @@ export class SessionManager {
360
460
  let source = 'user';
361
461
  // Determine source from path (normalize separators for cross-platform)
362
462
  const normalized = dir.split(path.sep).join('/');
363
- if (normalized.includes('.copilot/skills'))
463
+ if (normalized.includes('installed-plugins') && normalized.includes('/skills'))
464
+ source = 'plugin';
465
+ else if (normalized.includes('.copilot/skills'))
364
466
  source = 'user';
365
467
  else if (home && normalized.startsWith(home.split(path.sep).join('/') + '/.agents/skills'))
366
468
  source = 'user';
@@ -382,6 +484,12 @@ export class SessionManager {
382
484
  }
383
485
  return skills.sort((a, b) => a.name.localeCompare(b.name));
384
486
  }
487
+ /** Get info about configured hooks for a channel's workspace. */
488
+ getHooksInfo(channelId) {
489
+ const workingDirectory = this.resolveWorkingDirectory(channelId);
490
+ const allowWorkspaceHooks = getConfig().defaults.allowWorkspaceHooks ?? false;
491
+ return getHooksInfo(workingDirectory, { allowWorkspaceHooks });
492
+ }
385
493
  /** List tools the SDK CLI process has available (via server RPC). */
386
494
  async listSessionTools(channelId) {
387
495
  // Ensure there's an active session so the CLI process is running
@@ -781,7 +889,7 @@ export class SessionManager {
781
889
  if (!queue || queue.length === 0)
782
890
  return false;
783
891
  const pending = queue.shift();
784
- if (remember) {
892
+ if (remember && !pending.fromHook) {
785
893
  const action = allow ? 'allow' : 'deny';
786
894
  if (pending.serverName) {
787
895
  // MCP tool: save at server level so all tools on this server are covered
@@ -813,6 +921,8 @@ export class SessionManager {
813
921
  serverName: next.serverName,
814
922
  input: next.toolInput,
815
923
  commands: next.commands,
924
+ fromHook: next.fromHook,
925
+ hookReason: next.hookReason,
816
926
  },
817
927
  });
818
928
  }
@@ -847,6 +957,11 @@ export class SessionManager {
847
957
  const queue = this.pendingPermissions.get(channelId);
848
958
  return !!queue && queue.length > 0;
849
959
  }
960
+ /** Check if the current pending permission is from a hook (no remember allowed). */
961
+ isHookPermission(channelId) {
962
+ const queue = this.pendingPermissions.get(channelId);
963
+ return !!queue && queue.length > 0 && !!queue[0].fromHook;
964
+ }
850
965
  /** Get the current session ID for a channel (if any). */
851
966
  getSessionId(channelId) {
852
967
  return this.channelSessions.get(channelId) ?? getChannelSession(channelId) ?? undefined;
@@ -931,6 +1046,11 @@ export class SessionManager {
931
1046
  log.warn('Failed to fetch model list for fallback resolution');
932
1047
  }
933
1048
  const resolvedMcpServers = this.resolveMcpServers(workingDirectory);
1049
+ const rawHooks = await this.resolveHooks(workingDirectory);
1050
+ const hooks = this.wrapHooksWithAsk(rawHooks, channelId);
1051
+ if (hooks) {
1052
+ log.debug(`Hooks resolved for session create: ${Object.keys(hooks).join(', ')}`);
1053
+ }
934
1054
  const createWithModel = async (model) => {
935
1055
  return withWorkspaceEnv(workingDirectory, () => this.bridge.createSession({
936
1056
  model,
@@ -942,6 +1062,7 @@ export class SessionManager {
942
1062
  onPermissionRequest: (request, invocation) => this.handlePermissionRequest(channelId, request, invocation),
943
1063
  onUserInputRequest: (request, invocation) => this.handleUserInputRequest(channelId, request, invocation),
944
1064
  tools: customTools.length > 0 ? customTools : undefined,
1065
+ hooks,
945
1066
  }));
946
1067
  };
947
1068
  const { result: session, usedModel, didFallback } = await tryWithFallback(prefs.model, availableModels, configFallbacks, createWithModel);
@@ -974,6 +1095,11 @@ export class SessionManager {
974
1095
  const skillDirectories = discoverSkillDirectories(workingDirectory);
975
1096
  const customTools = this.buildCustomTools(channelId);
976
1097
  const mcpServers = this.resolveMcpServers(workingDirectory);
1098
+ const rawHooks = await this.resolveHooks(workingDirectory);
1099
+ const hooks = this.wrapHooksWithAsk(rawHooks, channelId);
1100
+ if (hooks) {
1101
+ log.debug(`Hooks resolved for session resume: ${Object.keys(hooks).join(', ')}`);
1102
+ }
977
1103
  const session = await withWorkspaceEnv(workingDirectory, () => this.bridge.resumeSession(sessionId, {
978
1104
  onPermissionRequest: (request, invocation) => this.handlePermissionRequest(channelId, request, invocation),
979
1105
  onUserInputRequest: (request, invocation) => this.handleUserInputRequest(channelId, request, invocation),
@@ -983,6 +1109,7 @@ export class SessionManager {
983
1109
  mcpServers,
984
1110
  skillDirectories: skillDirectories.length > 0 ? skillDirectories : undefined,
985
1111
  tools: customTools.length > 0 ? customTools : undefined,
1112
+ hooks,
986
1113
  }));
987
1114
  this.sessionMcpServers.set(channelId, new Set(Object.keys(mcpServers)));
988
1115
  this.sessionSkillDirs.set(channelId, new Set(skillDirectories));
@@ -1019,6 +1146,7 @@ export class SessionManager {
1019
1146
  }
1020
1147
  const defaultConfigDir = process.env.HOME ? `${process.env.HOME}/.copilot` : undefined;
1021
1148
  const skillDirectories = discoverSkillDirectories(targetWorkspace);
1149
+ const hooks = await this.resolveHooks(targetWorkspace);
1022
1150
  // Build ephemeral permission handler
1023
1151
  const ephemeralPermissionHandler = this.buildEphemeralPermissionHandler(opts);
1024
1152
  // Build custom tools for ephemeral session (ask_agent with propagated context)
@@ -1034,6 +1162,7 @@ export class SessionManager {
1034
1162
  onPermissionRequest: ephemeralPermissionHandler,
1035
1163
  systemMessage: { content: systemParts.filter(Boolean).join('\n\n') },
1036
1164
  tools: ephemeralTools.length > 0 ? ephemeralTools : undefined,
1165
+ hooks,
1037
1166
  }));
1038
1167
  // Send message and wait for idle
1039
1168
  const response = await this.sendAndWaitForIdle(session, opts.message, timeout);