@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.
- package/README.md +2 -1
- package/dist/channels/mattermost/streaming.d.ts +2 -0
- package/dist/channels/mattermost/streaming.d.ts.map +1 -1
- package/dist/channels/mattermost/streaming.js +5 -0
- package/dist/channels/mattermost/streaming.js.map +1 -1
- package/dist/core/bridge.d.ts +3 -0
- package/dist/core/bridge.d.ts.map +1 -1
- package/dist/core/bridge.js +2 -0
- package/dist/core/bridge.js.map +1 -1
- package/dist/core/command-handler.d.ts +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +11 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/hooks-loader.d.ts +74 -0
- package/dist/core/hooks-loader.d.ts.map +1 -0
- package/dist/core/hooks-loader.js +302 -0
- package/dist/core/hooks-loader.js.map +1 -0
- package/dist/core/session-manager.d.ts +13 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +131 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/stream-formatter.d.ts +1 -1
- package/dist/core/stream-formatter.d.ts.map +1 -1
- package/dist/core/stream-formatter.js +14 -5
- package/dist/core/stream-formatter.js.map +1 -1
- package/dist/index.js +74 -9
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EACV,cAAc,EACf,MAAM,aAAa,CAAC;AAIrB,4GAA4G;AAC5G,eAAO,MAAM,mBAAmB,UAA8D,CAAC;AAE/F,KAAK,mBAAmB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;AAsNtF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAU/D;
|
|
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('.
|
|
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);
|