@code-yeongyu/senpi 2026.6.28-4 → 2026.6.30
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/CHANGELOG.md +15 -0
- package/README.md +2 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +17 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/command-runner.d.ts +25 -0
- package/dist/core/extensions/builtin/hooks/command-runner.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/command-runner.js +152 -0
- package/dist/core/extensions/builtin/hooks/command-runner.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/command.d.ts +11 -0
- package/dist/core/extensions/builtin/hooks/command.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/command.js +180 -0
- package/dist/core/extensions/builtin/hooks/command.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/config-loader.d.ts +19 -0
- package/dist/core/extensions/builtin/hooks/config-loader.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/config-loader.js +125 -0
- package/dist/core/extensions/builtin/hooks/config-loader.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.d.ts +10 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.js +14 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.d.ts +60 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.js +161 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/handler.d.ts +12 -0
- package/dist/core/extensions/builtin/hooks/handler.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/handler.js +128 -0
- package/dist/core/extensions/builtin/hooks/handler.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/index.d.ts +6 -0
- package/dist/core/extensions/builtin/hooks/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/index.js +216 -0
- package/dist/core/extensions/builtin/hooks/index.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts +29 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js +276 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/matcher.d.ts +7 -0
- package/dist/core/extensions/builtin/hooks/matcher.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/matcher.js +109 -0
- package/dist/core/extensions/builtin/hooks/matcher.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.d.ts +29 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.js +47 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-parser.d.ts +27 -0
- package/dist/core/extensions/builtin/hooks/output-parser.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-parser.js +168 -0
- package/dist/core/extensions/builtin/hooks/output-parser.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.d.ts +11 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.js +138 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.d.ts +47 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js +86 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.d.ts +30 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.js +59 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/safety.d.ts +14 -0
- package/dist/core/extensions/builtin/hooks/safety.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/safety.js +179 -0
- package/dist/core/extensions/builtin/hooks/safety.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/schema.d.ts +3 -0
- package/dist/core/extensions/builtin/hooks/schema.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/schema.js +94 -0
- package/dist/core/extensions/builtin/hooks/schema.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.d.ts +18 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.js +282 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.d.ts +11 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.js +158 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts +24 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.js +102 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust.d.ts +34 -0
- package/dist/core/extensions/builtin/hooks/trust.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust.js +194 -0
- package/dist/core/extensions/builtin/hooks/trust.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/types.d.ts +134 -0
- package/dist/core/extensions/builtin/hooks/types.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/types.js +36 -0
- package/dist/core/extensions/builtin/hooks/types.js.map +1 -0
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +2 -0
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +20 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +17 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +14 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts +14 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +38 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/version-check.d.ts +1 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +7 -0
- package/dist/utils/version-check.js.map +1 -1
- package/docs/extensions.md +450 -0
- package/docs/index.md +1 -1
- package/docs/packages.md +9 -6
- package/examples/extensions/README.md +1 -0
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +17 -24
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +17 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +17 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +0 -24
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +16 -35
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +12 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +12 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +12 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js +15 -15
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +3 -0
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/npm-shrinkwrap.json +9 -9
- package/package.json +4 -4
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type HookOutputPolicy, type HookOutputSafetyMetadata } from "./safety.ts";
|
|
2
|
+
import type { ExecutableHookHandler, HookInputWire } from "./types.ts";
|
|
3
|
+
export type CommandHookRunOptions = {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly envPassthrough?: readonly string[];
|
|
6
|
+
readonly outputPolicy?: HookOutputPolicy;
|
|
7
|
+
readonly signal?: AbortSignal;
|
|
8
|
+
readonly sourceEnv?: NodeJS.ProcessEnv;
|
|
9
|
+
};
|
|
10
|
+
export type CommandHookRunResult = {
|
|
11
|
+
readonly command: string;
|
|
12
|
+
readonly cwd: string;
|
|
13
|
+
readonly stdout: string;
|
|
14
|
+
readonly stderr: string;
|
|
15
|
+
readonly exitCode: number | null;
|
|
16
|
+
readonly signal: NodeJS.Signals | null;
|
|
17
|
+
readonly timedOut: boolean;
|
|
18
|
+
readonly aborted: boolean;
|
|
19
|
+
readonly durationMs: number;
|
|
20
|
+
readonly outputSafety: HookOutputSafetyMetadata;
|
|
21
|
+
readonly timeoutSeconds: number;
|
|
22
|
+
};
|
|
23
|
+
export declare function runCommandHook(handler: ExecutableHookHandler, input: HookInputWire, options: CommandHookRunOptions): Promise<CommandHookRunResult>;
|
|
24
|
+
export declare function selectCommandForPlatform(handler: ExecutableHookHandler, platform?: NodeJS.Platform): string;
|
|
25
|
+
//# sourceMappingURL=command-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-runner.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/command-runner.ts"],"names":[],"mappings":"AAIA,OAAO,EAKN,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAE7B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,MAAM,qBAAqB,GAAG;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,wBAAwB,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,wBAAsB,cAAc,CACnC,OAAO,EAAE,qBAAqB,EAC9B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAqF/B;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC1C,MAAM,CAKR"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
3
|
+
import { waitForChildProcess } from "../../../../utils/child-process.js";
|
|
4
|
+
import { killProcessTree, trackDetachedChildPid, untrackDetachedChildPid } from "../../../../utils/shell.js";
|
|
5
|
+
import { applyHookOutputSafety, buildHookEnvironment, DEFAULT_STDERR_LIMIT_BYTES, DEFAULT_STDOUT_LIMIT_BYTES, resolveHookTimeoutSeconds, } from "./safety.js";
|
|
6
|
+
export async function runCommandHook(handler, input, options) {
|
|
7
|
+
const command = selectCommandForPlatform(handler);
|
|
8
|
+
const startedAt = performance.now();
|
|
9
|
+
const timeoutSeconds = resolveHookTimeoutSeconds(handler);
|
|
10
|
+
if (options.signal?.aborted) {
|
|
11
|
+
return buildResult({
|
|
12
|
+
aborted: true,
|
|
13
|
+
command,
|
|
14
|
+
cwd: options.cwd,
|
|
15
|
+
exitCode: null,
|
|
16
|
+
signal: null,
|
|
17
|
+
startedAt,
|
|
18
|
+
stderr: createEmptyStreamCapture("stderr"),
|
|
19
|
+
stdout: createEmptyStreamCapture("stdout"),
|
|
20
|
+
timedOut: false,
|
|
21
|
+
timeoutSeconds,
|
|
22
|
+
outputPolicy: options.outputPolicy,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const child = spawn(command, {
|
|
26
|
+
cwd: options.cwd,
|
|
27
|
+
detached: process.platform !== "win32",
|
|
28
|
+
env: buildHookEnvironment({
|
|
29
|
+
envPassthrough: options.envPassthrough,
|
|
30
|
+
handler,
|
|
31
|
+
input,
|
|
32
|
+
sourceEnv: options.sourceEnv ?? process.env,
|
|
33
|
+
}),
|
|
34
|
+
shell: true,
|
|
35
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
});
|
|
38
|
+
if (child.pid !== undefined)
|
|
39
|
+
trackDetachedChildPid(child.pid);
|
|
40
|
+
const stdout = createBoundedStreamCapture("stdout", options.outputPolicy);
|
|
41
|
+
const stderr = createBoundedStreamCapture("stderr", options.outputPolicy);
|
|
42
|
+
let exitSignal = null;
|
|
43
|
+
let timedOut = false;
|
|
44
|
+
let timeoutHandle;
|
|
45
|
+
const killChild = () => {
|
|
46
|
+
if (child.pid !== undefined)
|
|
47
|
+
killProcessTree(child.pid);
|
|
48
|
+
};
|
|
49
|
+
const onAbort = () => killChild();
|
|
50
|
+
try {
|
|
51
|
+
child.stdout?.on("data", (chunk) => stdout.append(chunk));
|
|
52
|
+
child.stderr?.on("data", (chunk) => stderr.append(chunk));
|
|
53
|
+
child.once("exit", (_code, signal) => {
|
|
54
|
+
exitSignal = signal;
|
|
55
|
+
});
|
|
56
|
+
if (timeoutSeconds > 0) {
|
|
57
|
+
timeoutHandle = setTimeout(() => {
|
|
58
|
+
timedOut = true;
|
|
59
|
+
killChild();
|
|
60
|
+
}, timeoutSeconds * 1000);
|
|
61
|
+
}
|
|
62
|
+
if (options.signal !== undefined) {
|
|
63
|
+
if (options.signal.aborted)
|
|
64
|
+
onAbort();
|
|
65
|
+
else
|
|
66
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
67
|
+
}
|
|
68
|
+
child.stdin?.end(JSON.stringify(input));
|
|
69
|
+
const exitCode = await waitForChildProcess(child);
|
|
70
|
+
return buildResult({
|
|
71
|
+
aborted: options.signal?.aborted === true,
|
|
72
|
+
command,
|
|
73
|
+
cwd: options.cwd,
|
|
74
|
+
exitCode: timedOut || options.signal?.aborted === true ? null : exitCode,
|
|
75
|
+
signal: exitSignal,
|
|
76
|
+
startedAt,
|
|
77
|
+
stderr,
|
|
78
|
+
stdout,
|
|
79
|
+
timedOut,
|
|
80
|
+
timeoutSeconds,
|
|
81
|
+
outputPolicy: options.outputPolicy,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
if (child.pid !== undefined)
|
|
86
|
+
untrackDetachedChildPid(child.pid);
|
|
87
|
+
if (timeoutHandle !== undefined)
|
|
88
|
+
clearTimeout(timeoutHandle);
|
|
89
|
+
if (options.signal !== undefined)
|
|
90
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function selectCommandForPlatform(handler, platform = process.platform) {
|
|
94
|
+
if (platform === "win32" && handler.config.commandWindows !== undefined) {
|
|
95
|
+
return handler.config.commandWindows;
|
|
96
|
+
}
|
|
97
|
+
return handler.config.command;
|
|
98
|
+
}
|
|
99
|
+
function buildResult(input) {
|
|
100
|
+
const stdout = input.stdout.finalize(input.outputPolicy);
|
|
101
|
+
const stderr = input.stderr.finalize(input.outputPolicy);
|
|
102
|
+
return {
|
|
103
|
+
aborted: input.aborted,
|
|
104
|
+
command: input.command,
|
|
105
|
+
cwd: input.cwd,
|
|
106
|
+
durationMs: Math.max(0, performance.now() - input.startedAt),
|
|
107
|
+
exitCode: input.exitCode,
|
|
108
|
+
outputSafety: { stderr: stderr.safety, stdout: stdout.safety },
|
|
109
|
+
signal: input.signal,
|
|
110
|
+
stderr: stderr.text,
|
|
111
|
+
stdout: stdout.text,
|
|
112
|
+
timedOut: input.timedOut,
|
|
113
|
+
timeoutSeconds: input.timeoutSeconds,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function createBoundedStreamCapture(stream, policy) {
|
|
117
|
+
const limit = stream === "stdout"
|
|
118
|
+
? (policy?.maxStdoutBytes ?? DEFAULT_STDOUT_LIMIT_BYTES)
|
|
119
|
+
: (policy?.maxStderrBytes ?? DEFAULT_STDERR_LIMIT_BYTES);
|
|
120
|
+
const chunks = [];
|
|
121
|
+
let keptBytes = 0;
|
|
122
|
+
let totalBytes = 0;
|
|
123
|
+
return {
|
|
124
|
+
append(chunk) {
|
|
125
|
+
totalBytes += chunk.length;
|
|
126
|
+
if (keptBytes >= limit)
|
|
127
|
+
return;
|
|
128
|
+
const bytesToKeep = Math.min(limit - keptBytes, chunk.length);
|
|
129
|
+
chunks.push(Buffer.from(chunk.subarray(0, bytesToKeep)));
|
|
130
|
+
keptBytes += bytesToKeep;
|
|
131
|
+
},
|
|
132
|
+
finalize(finalPolicy) {
|
|
133
|
+
let text = "";
|
|
134
|
+
for (const chunk of chunks) {
|
|
135
|
+
text += chunk.toString("utf8");
|
|
136
|
+
}
|
|
137
|
+
return applyHookOutputSafety(stream, text, finalPolicy, {
|
|
138
|
+
originalBytes: totalBytes,
|
|
139
|
+
truncated: totalBytes > keptBytes,
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function createEmptyStreamCapture(stream) {
|
|
145
|
+
return {
|
|
146
|
+
append(_chunk) { },
|
|
147
|
+
finalize(policy) {
|
|
148
|
+
return applyHookOutputSafety(stream, "", policy, { originalBytes: 0, truncated: false });
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=command-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-runner.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/command-runner.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { performance } from \"node:perf_hooks\";\nimport { waitForChildProcess } from \"../../../../utils/child-process.ts\";\nimport { killProcessTree, trackDetachedChildPid, untrackDetachedChildPid } from \"../../../../utils/shell.ts\";\nimport {\n\tapplyHookOutputSafety,\n\tbuildHookEnvironment,\n\tDEFAULT_STDERR_LIMIT_BYTES,\n\tDEFAULT_STDOUT_LIMIT_BYTES,\n\ttype HookOutputPolicy,\n\ttype HookOutputSafetyMetadata,\n\tresolveHookTimeoutSeconds,\n} from \"./safety.ts\";\nimport type { ExecutableHookHandler, HookInputWire } from \"./types.ts\";\n\nexport type CommandHookRunOptions = {\n\treadonly cwd: string;\n\treadonly envPassthrough?: readonly string[];\n\treadonly outputPolicy?: HookOutputPolicy;\n\treadonly signal?: AbortSignal;\n\treadonly sourceEnv?: NodeJS.ProcessEnv;\n};\n\nexport type CommandHookRunResult = {\n\treadonly command: string;\n\treadonly cwd: string;\n\treadonly stdout: string;\n\treadonly stderr: string;\n\treadonly exitCode: number | null;\n\treadonly signal: NodeJS.Signals | null;\n\treadonly timedOut: boolean;\n\treadonly aborted: boolean;\n\treadonly durationMs: number;\n\treadonly outputSafety: HookOutputSafetyMetadata;\n\treadonly timeoutSeconds: number;\n};\n\nexport async function runCommandHook(\n\thandler: ExecutableHookHandler,\n\tinput: HookInputWire,\n\toptions: CommandHookRunOptions,\n): Promise<CommandHookRunResult> {\n\tconst command = selectCommandForPlatform(handler);\n\tconst startedAt = performance.now();\n\tconst timeoutSeconds = resolveHookTimeoutSeconds(handler);\n\tif (options.signal?.aborted) {\n\t\treturn buildResult({\n\t\t\taborted: true,\n\t\t\tcommand,\n\t\t\tcwd: options.cwd,\n\t\t\texitCode: null,\n\t\t\tsignal: null,\n\t\t\tstartedAt,\n\t\t\tstderr: createEmptyStreamCapture(\"stderr\"),\n\t\t\tstdout: createEmptyStreamCapture(\"stdout\"),\n\t\t\ttimedOut: false,\n\t\t\ttimeoutSeconds,\n\t\t\toutputPolicy: options.outputPolicy,\n\t\t});\n\t}\n\n\tconst child = spawn(command, {\n\t\tcwd: options.cwd,\n\t\tdetached: process.platform !== \"win32\",\n\t\tenv: buildHookEnvironment({\n\t\t\tenvPassthrough: options.envPassthrough,\n\t\t\thandler,\n\t\t\tinput,\n\t\t\tsourceEnv: options.sourceEnv ?? process.env,\n\t\t}),\n\t\tshell: true,\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\twindowsHide: true,\n\t});\n\tif (child.pid !== undefined) trackDetachedChildPid(child.pid);\n\n\tconst stdout = createBoundedStreamCapture(\"stdout\", options.outputPolicy);\n\tconst stderr = createBoundedStreamCapture(\"stderr\", options.outputPolicy);\n\tlet exitSignal: NodeJS.Signals | null = null;\n\tlet timedOut = false;\n\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\n\tconst killChild = (): void => {\n\t\tif (child.pid !== undefined) killProcessTree(child.pid);\n\t};\n\tconst onAbort = (): void => killChild();\n\n\ttry {\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer) => stdout.append(chunk));\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer) => stderr.append(chunk));\n\t\tchild.once(\"exit\", (_code, signal) => {\n\t\t\texitSignal = signal;\n\t\t});\n\n\t\tif (timeoutSeconds > 0) {\n\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\ttimedOut = true;\n\t\t\t\tkillChild();\n\t\t\t}, timeoutSeconds * 1000);\n\t\t}\n\n\t\tif (options.signal !== undefined) {\n\t\t\tif (options.signal.aborted) onAbort();\n\t\t\telse options.signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\n\t\tchild.stdin?.end(JSON.stringify(input));\n\t\tconst exitCode = await waitForChildProcess(child);\n\t\treturn buildResult({\n\t\t\taborted: options.signal?.aborted === true,\n\t\t\tcommand,\n\t\t\tcwd: options.cwd,\n\t\t\texitCode: timedOut || options.signal?.aborted === true ? null : exitCode,\n\t\t\tsignal: exitSignal,\n\t\t\tstartedAt,\n\t\t\tstderr,\n\t\t\tstdout,\n\t\t\ttimedOut,\n\t\t\ttimeoutSeconds,\n\t\t\toutputPolicy: options.outputPolicy,\n\t\t});\n\t} finally {\n\t\tif (child.pid !== undefined) untrackDetachedChildPid(child.pid);\n\t\tif (timeoutHandle !== undefined) clearTimeout(timeoutHandle);\n\t\tif (options.signal !== undefined) options.signal.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nexport function selectCommandForPlatform(\n\thandler: ExecutableHookHandler,\n\tplatform: NodeJS.Platform = process.platform,\n): string {\n\tif (platform === \"win32\" && handler.config.commandWindows !== undefined) {\n\t\treturn handler.config.commandWindows;\n\t}\n\treturn handler.config.command;\n}\n\nfunction buildResult(input: {\n\treadonly command: string;\n\treadonly cwd: string;\n\treadonly exitCode: number | null;\n\treadonly signal: NodeJS.Signals | null;\n\treadonly timedOut: boolean;\n\treadonly aborted: boolean;\n\treadonly startedAt: number;\n\treadonly stderr: BoundedStreamCapture;\n\treadonly stdout: BoundedStreamCapture;\n\treadonly timeoutSeconds: number;\n\treadonly outputPolicy?: HookOutputPolicy;\n}): CommandHookRunResult {\n\tconst stdout = input.stdout.finalize(input.outputPolicy);\n\tconst stderr = input.stderr.finalize(input.outputPolicy);\n\treturn {\n\t\taborted: input.aborted,\n\t\tcommand: input.command,\n\t\tcwd: input.cwd,\n\t\tdurationMs: Math.max(0, performance.now() - input.startedAt),\n\t\texitCode: input.exitCode,\n\t\toutputSafety: { stderr: stderr.safety, stdout: stdout.safety },\n\t\tsignal: input.signal,\n\t\tstderr: stderr.text,\n\t\tstdout: stdout.text,\n\t\ttimedOut: input.timedOut,\n\t\ttimeoutSeconds: input.timeoutSeconds,\n\t};\n}\n\ntype BoundedStreamCapture = {\n\treadonly append: (chunk: Buffer) => void;\n\treadonly finalize: (policy: HookOutputPolicy | undefined) => ReturnType<typeof applyHookOutputSafety>;\n};\n\nfunction createBoundedStreamCapture(\n\tstream: \"stderr\" | \"stdout\",\n\tpolicy: HookOutputPolicy | undefined,\n): BoundedStreamCapture {\n\tconst limit =\n\t\tstream === \"stdout\"\n\t\t\t? (policy?.maxStdoutBytes ?? DEFAULT_STDOUT_LIMIT_BYTES)\n\t\t\t: (policy?.maxStderrBytes ?? DEFAULT_STDERR_LIMIT_BYTES);\n\tconst chunks: Buffer[] = [];\n\tlet keptBytes = 0;\n\tlet totalBytes = 0;\n\n\treturn {\n\t\tappend(chunk: Buffer): void {\n\t\t\ttotalBytes += chunk.length;\n\t\t\tif (keptBytes >= limit) return;\n\t\t\tconst bytesToKeep = Math.min(limit - keptBytes, chunk.length);\n\t\t\tchunks.push(Buffer.from(chunk.subarray(0, bytesToKeep)));\n\t\t\tkeptBytes += bytesToKeep;\n\t\t},\n\t\tfinalize(finalPolicy: HookOutputPolicy | undefined): ReturnType<typeof applyHookOutputSafety> {\n\t\t\tlet text = \"\";\n\t\t\tfor (const chunk of chunks) {\n\t\t\t\ttext += chunk.toString(\"utf8\");\n\t\t\t}\n\t\t\treturn applyHookOutputSafety(stream, text, finalPolicy, {\n\t\t\t\toriginalBytes: totalBytes,\n\t\t\t\ttruncated: totalBytes > keptBytes,\n\t\t\t});\n\t\t},\n\t};\n}\n\nfunction createEmptyStreamCapture(stream: \"stderr\" | \"stdout\"): BoundedStreamCapture {\n\treturn {\n\t\tappend(_chunk: Buffer): void {},\n\t\tfinalize(policy: HookOutputPolicy | undefined): ReturnType<typeof applyHookOutputSafety> {\n\t\t\treturn applyHookOutputSafety(stream, \"\", policy, { originalBytes: 0, truncated: false });\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "../../types.ts";
|
|
2
|
+
import type { HookStateStorage } from "./trust-storage.ts";
|
|
3
|
+
import type { HookDiagnostic, HookRuntimeState } from "./types.ts";
|
|
4
|
+
type HookCommandRuntimeState = HookRuntimeState & {
|
|
5
|
+
readonly storage: HookStateStorage;
|
|
6
|
+
};
|
|
7
|
+
export declare function registerHooksCommand(pi: ExtensionAPI, refreshState: (ctx: ExtensionContext) => HookCommandRuntimeState): void;
|
|
8
|
+
export declare function formatHookStatus(state: HookCommandRuntimeState): string;
|
|
9
|
+
export declare function formatHookDiagnostics(diagnostics: readonly HookDiagnostic[]): string;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAA2B,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAG9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAEX,cAAc,EACd,gBAAgB,EAIhB,MAAM,YAAY,CAAC;AAMpB,KAAK,uBAAuB,GAAG,gBAAgB,GAAG;IACjD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;CACnC,CAAC;AAcF,wBAAgB,oBAAoB,CACnC,EAAE,EAAE,YAAY,EAChB,YAAY,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,uBAAuB,GAC9D,IAAI,CAoCN;AA8FD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAgBvE;AA6BD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAgBpF"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { redactHookTokenValues } from "./output-bounds.js";
|
|
2
|
+
import { createHookTrustEntry, hookTrustStorageScope, listHookTrustRecords } from "./trust.js";
|
|
3
|
+
const HOOKS_USAGE = "Usage: /hooks [list|diagnostics|trust <id>|disable <id>|enable <id>|reload]";
|
|
4
|
+
const HOOK_SUBCOMMANDS = ["list", "diagnostics", "trust", "disable", "enable", "reload"];
|
|
5
|
+
const DIAGNOSTIC_DISPLAY_LIMIT = 50;
|
|
6
|
+
export function registerHooksCommand(pi, refreshState) {
|
|
7
|
+
pi.registerCommand("hooks", {
|
|
8
|
+
description: "Inspect loaded builtin hook sources and diagnostics.",
|
|
9
|
+
getArgumentCompletions: async (prefix) => hookArgumentCompletions(prefix),
|
|
10
|
+
handler: async (args, ctx) => {
|
|
11
|
+
const command = parseHooksCommand(args);
|
|
12
|
+
if (command.kind === "usage") {
|
|
13
|
+
ctx.ui.notify(HOOKS_USAGE, "error");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (command.kind === "reload") {
|
|
17
|
+
await ctx.reload();
|
|
18
|
+
ctx.ui.notify(`Reloaded hooks.\n${formatHookStatus(refreshState(ctx))}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const state = refreshState(ctx);
|
|
22
|
+
switch (command.kind) {
|
|
23
|
+
case "list":
|
|
24
|
+
ctx.ui.notify(formatHookStatus(state));
|
|
25
|
+
return;
|
|
26
|
+
case "diagnostics":
|
|
27
|
+
ctx.ui.notify(formatHookDiagnostics(state.parsed.diagnostics), state.parsed.diagnostics.length > 0 ? "warning" : "info");
|
|
28
|
+
return;
|
|
29
|
+
case "trust":
|
|
30
|
+
case "disable":
|
|
31
|
+
case "enable":
|
|
32
|
+
mutateHookTrust(command.kind, command.id, state, ctx);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function hookArgumentCompletions(prefix) {
|
|
39
|
+
if (/\s/.test(prefix)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const completions = HOOK_SUBCOMMANDS.filter((subcommand) => subcommand.startsWith(prefix)).map((subcommand) => ({
|
|
43
|
+
value: subcommand,
|
|
44
|
+
label: subcommand,
|
|
45
|
+
}));
|
|
46
|
+
return completions.length > 0 ? completions : null;
|
|
47
|
+
}
|
|
48
|
+
function parseHooksCommand(args) {
|
|
49
|
+
const tokens = args.trim().length === 0 ? [] : args.trim().split(/\s+/);
|
|
50
|
+
const subcommand = tokens[0] ?? "";
|
|
51
|
+
if (subcommand === "" || subcommand === "list") {
|
|
52
|
+
return tokens.length <= 1 ? { kind: "list" } : { kind: "usage" };
|
|
53
|
+
}
|
|
54
|
+
if (subcommand === "diagnostics" || subcommand === "reload") {
|
|
55
|
+
return tokens.length === 1 ? { kind: subcommand } : { kind: "usage" };
|
|
56
|
+
}
|
|
57
|
+
if (subcommand === "trust" || subcommand === "disable" || subcommand === "enable") {
|
|
58
|
+
const id = tokens[1];
|
|
59
|
+
return tokens.length === 2 && id !== undefined ? { kind: subcommand, id } : { kind: "usage" };
|
|
60
|
+
}
|
|
61
|
+
return { kind: "usage" };
|
|
62
|
+
}
|
|
63
|
+
function mutateHookTrust(action, id, state, ctx) {
|
|
64
|
+
const pair = findHookRecordPair(state, id);
|
|
65
|
+
if (pair === undefined) {
|
|
66
|
+
ctx.ui.notify(`Hook not found: ${id}`, "error");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const scope = hookTrustStorageScope(pair.handler, { projectTrusted: ctx.isProjectTrusted() });
|
|
70
|
+
if (scope === undefined) {
|
|
71
|
+
ctx.ui.notify(`Hook not found: ${id}`, "error");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const entry = entryForAction(action, pair);
|
|
75
|
+
state.storage.update(scope, (current) => ({
|
|
76
|
+
version: 1,
|
|
77
|
+
hooks: {
|
|
78
|
+
...current.hooks,
|
|
79
|
+
[id]: entry,
|
|
80
|
+
},
|
|
81
|
+
}));
|
|
82
|
+
ctx.ui.notify(`${pastTenseAction(action)} hook: ${id}`);
|
|
83
|
+
}
|
|
84
|
+
function findHookRecordPair(state, id) {
|
|
85
|
+
const records = listHookTrustRecords(state.parsed.executableHandlers, state.trust, { platform: process.platform });
|
|
86
|
+
const index = records.findIndex((record) => record.id === id);
|
|
87
|
+
if (index < 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
const handler = state.parsed.executableHandlers[index];
|
|
90
|
+
const record = records[index];
|
|
91
|
+
if (handler === undefined || record === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
return { handler, record };
|
|
94
|
+
}
|
|
95
|
+
function entryForAction(action, pair) {
|
|
96
|
+
if (action === "trust") {
|
|
97
|
+
return createHookTrustEntry(pair.handler, { platform: process.platform });
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
enabled: action === "enable",
|
|
101
|
+
...(pair.record.entry?.trustedHash === undefined ? {} : { trustedHash: pair.record.entry.trustedHash }),
|
|
102
|
+
scope: pair.record.scope,
|
|
103
|
+
sourcePath: pair.record.sourcePath,
|
|
104
|
+
...(pair.record.matcher === undefined ? {} : { matcher: pair.record.matcher }),
|
|
105
|
+
commandPreview: pair.record.commandPreview,
|
|
106
|
+
updatedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function pastTenseAction(action) {
|
|
110
|
+
switch (action) {
|
|
111
|
+
case "trust":
|
|
112
|
+
return "Trusted";
|
|
113
|
+
case "disable":
|
|
114
|
+
return "Disabled";
|
|
115
|
+
case "enable":
|
|
116
|
+
return "Enabled";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function formatHookStatus(state) {
|
|
120
|
+
const parsed = state.parsed;
|
|
121
|
+
const counts = eventCounts(parsed);
|
|
122
|
+
const summary = counts.size === 0
|
|
123
|
+
? "hooks: no executable hooks"
|
|
124
|
+
: `hooks: ${parsed.executableHandlers.length} executable hooks (${Array.from(counts.entries())
|
|
125
|
+
.map(([event, count]) => `${event}:${count}`)
|
|
126
|
+
.join(", ")})`;
|
|
127
|
+
const records = listHookTrustRecords(parsed.executableHandlers, state.trust, { platform: process.platform });
|
|
128
|
+
if (records.length === 0) {
|
|
129
|
+
return summary;
|
|
130
|
+
}
|
|
131
|
+
return [summary, ...records.map((record, index) => formatHookRecord(parsed.executableHandlers[index], record))]
|
|
132
|
+
.filter((line) => line.length > 0)
|
|
133
|
+
.join("\n");
|
|
134
|
+
}
|
|
135
|
+
function eventCounts(parsed) {
|
|
136
|
+
const counts = new Map();
|
|
137
|
+
for (const handler of parsed.executableHandlers) {
|
|
138
|
+
counts.set(handler.event, (counts.get(handler.event) ?? 0) + 1);
|
|
139
|
+
}
|
|
140
|
+
return counts;
|
|
141
|
+
}
|
|
142
|
+
function formatHookRecord(handler, record) {
|
|
143
|
+
if (handler === undefined)
|
|
144
|
+
return "";
|
|
145
|
+
const parts = [
|
|
146
|
+
`- ${record.id}`,
|
|
147
|
+
handler.event,
|
|
148
|
+
`source:${record.scope}`,
|
|
149
|
+
`status:${record.trusted ? "trusted" : "untrusted"}`,
|
|
150
|
+
`disabled:${record.enabled ? "false" : "true"}`,
|
|
151
|
+
];
|
|
152
|
+
if (record.matcher !== undefined) {
|
|
153
|
+
parts.push(`matcher:${sanitizeDisplayText(record.matcher)}`);
|
|
154
|
+
}
|
|
155
|
+
if (handler.config.statusMessage !== undefined) {
|
|
156
|
+
parts.push(`statusMessage:${sanitizeDisplayText(handler.config.statusMessage)}`);
|
|
157
|
+
}
|
|
158
|
+
parts.push(`command:${sanitizeDisplayText(record.commandPreview)}`);
|
|
159
|
+
return parts.join(" ");
|
|
160
|
+
}
|
|
161
|
+
export function formatHookDiagnostics(diagnostics) {
|
|
162
|
+
if (diagnostics.length === 0) {
|
|
163
|
+
return "hooks diagnostics: none";
|
|
164
|
+
}
|
|
165
|
+
const visible = diagnostics.slice(0, DIAGNOSTIC_DISPLAY_LIMIT);
|
|
166
|
+
const lines = visible.map((diagnostic) => `${diagnostic.severity}: ${diagnostic.code} ${sanitizeDisplayText(diagnostic.source.sourcePath)} ${sanitizeDisplayText(diagnostic.path)} ${sanitizeDisplayText(diagnostic.message)}`);
|
|
167
|
+
const remaining = diagnostics.length - visible.length;
|
|
168
|
+
if (remaining > 0) {
|
|
169
|
+
lines.push(`... ${remaining} more diagnostics`);
|
|
170
|
+
}
|
|
171
|
+
return [`hooks diagnostics: ${diagnostics.length} diagnostics`, ...lines].join("\n");
|
|
172
|
+
}
|
|
173
|
+
function sanitizeDisplayText(value) {
|
|
174
|
+
return redactHookTokenValues(value
|
|
175
|
+
.replace(/(--(?:api-key|apikey|auth|authorization|password|secret|token)=)(?:"[^"]*"|'[^']*'|\S+)/gi, "$1[redacted]")
|
|
176
|
+
.replace(/(--(?:api-key|apikey|auth|authorization|password|secret|token)\s+)(?:"[^"]*"|'[^']*'|\S+)/gi, "$1[redacted]")
|
|
177
|
+
.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, "$1[redacted]")
|
|
178
|
+
.replace(/\b(?:sk|pk|rk|glpat)-[A-Za-z0-9._-]+/g, "[redacted]"), "[redacted]");
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/command.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from \"../../types.ts\";\nimport { redactHookTokenValues } from \"./output-bounds.ts\";\nimport { createHookTrustEntry, type HookTrustRecord, hookTrustStorageScope, listHookTrustRecords } from \"./trust.ts\";\nimport type { HookStateStorage } from \"./trust-storage.ts\";\nimport type {\n\tExecutableHookHandler,\n\tHookDiagnostic,\n\tHookRuntimeState,\n\tHookTrustEntry,\n\tParsedHookConfig,\n\tSupportedHookEvent,\n} from \"./types.ts\";\n\nconst HOOKS_USAGE = \"Usage: /hooks [list|diagnostics|trust <id>|disable <id>|enable <id>|reload]\";\nconst HOOK_SUBCOMMANDS = [\"list\", \"diagnostics\", \"trust\", \"disable\", \"enable\", \"reload\"] as const;\nconst DIAGNOSTIC_DISPLAY_LIMIT = 50;\n\ntype HookCommandRuntimeState = HookRuntimeState & {\n\treadonly storage: HookStateStorage;\n};\n\ntype HookRecordPair = {\n\treadonly handler: ExecutableHookHandler;\n\treadonly record: HookTrustRecord;\n};\n\ntype HookCommand =\n\t| { readonly kind: \"list\" }\n\t| { readonly kind: \"diagnostics\" }\n\t| { readonly kind: \"reload\" }\n\t| { readonly kind: \"trust\" | \"disable\" | \"enable\"; readonly id: string }\n\t| { readonly kind: \"usage\" };\n\nexport function registerHooksCommand(\n\tpi: ExtensionAPI,\n\trefreshState: (ctx: ExtensionContext) => HookCommandRuntimeState,\n): void {\n\tpi.registerCommand(\"hooks\", {\n\t\tdescription: \"Inspect loaded builtin hook sources and diagnostics.\",\n\t\tgetArgumentCompletions: async (prefix) => hookArgumentCompletions(prefix),\n\t\thandler: async (args, ctx) => {\n\t\t\tconst command = parseHooksCommand(args);\n\t\t\tif (command.kind === \"usage\") {\n\t\t\t\tctx.ui.notify(HOOKS_USAGE, \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (command.kind === \"reload\") {\n\t\t\t\tawait ctx.reload();\n\t\t\t\tctx.ui.notify(`Reloaded hooks.\\n${formatHookStatus(refreshState(ctx))}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst state = refreshState(ctx);\n\t\t\tswitch (command.kind) {\n\t\t\t\tcase \"list\":\n\t\t\t\t\tctx.ui.notify(formatHookStatus(state));\n\t\t\t\t\treturn;\n\t\t\t\tcase \"diagnostics\":\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\tformatHookDiagnostics(state.parsed.diagnostics),\n\t\t\t\t\t\tstate.parsed.diagnostics.length > 0 ? \"warning\" : \"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"trust\":\n\t\t\t\tcase \"disable\":\n\t\t\t\tcase \"enable\":\n\t\t\t\t\tmutateHookTrust(command.kind, command.id, state, ctx);\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t},\n\t});\n}\n\nfunction hookArgumentCompletions(prefix: string): Array<{ value: string; label: string }> | null {\n\tif (/\\s/.test(prefix)) {\n\t\treturn [];\n\t}\n\tconst completions = HOOK_SUBCOMMANDS.filter((subcommand) => subcommand.startsWith(prefix)).map((subcommand) => ({\n\t\tvalue: subcommand,\n\t\tlabel: subcommand,\n\t}));\n\treturn completions.length > 0 ? completions : null;\n}\n\nfunction parseHooksCommand(args: string): HookCommand {\n\tconst tokens = args.trim().length === 0 ? [] : args.trim().split(/\\s+/);\n\tconst subcommand = tokens[0] ?? \"\";\n\tif (subcommand === \"\" || subcommand === \"list\") {\n\t\treturn tokens.length <= 1 ? { kind: \"list\" } : { kind: \"usage\" };\n\t}\n\tif (subcommand === \"diagnostics\" || subcommand === \"reload\") {\n\t\treturn tokens.length === 1 ? { kind: subcommand } : { kind: \"usage\" };\n\t}\n\tif (subcommand === \"trust\" || subcommand === \"disable\" || subcommand === \"enable\") {\n\t\tconst id = tokens[1];\n\t\treturn tokens.length === 2 && id !== undefined ? { kind: subcommand, id } : { kind: \"usage\" };\n\t}\n\treturn { kind: \"usage\" };\n}\n\nfunction mutateHookTrust(\n\taction: \"trust\" | \"disable\" | \"enable\",\n\tid: string,\n\tstate: HookCommandRuntimeState,\n\tctx: ExtensionCommandContext,\n): void {\n\tconst pair = findHookRecordPair(state, id);\n\tif (pair === undefined) {\n\t\tctx.ui.notify(`Hook not found: ${id}`, \"error\");\n\t\treturn;\n\t}\n\n\tconst scope = hookTrustStorageScope(pair.handler, { projectTrusted: ctx.isProjectTrusted() });\n\tif (scope === undefined) {\n\t\tctx.ui.notify(`Hook not found: ${id}`, \"error\");\n\t\treturn;\n\t}\n\n\tconst entry = entryForAction(action, pair);\n\tstate.storage.update(scope, (current) => ({\n\t\tversion: 1,\n\t\thooks: {\n\t\t\t...current.hooks,\n\t\t\t[id]: entry,\n\t\t},\n\t}));\n\tctx.ui.notify(`${pastTenseAction(action)} hook: ${id}`);\n}\n\nfunction findHookRecordPair(state: HookCommandRuntimeState, id: string): HookRecordPair | undefined {\n\tconst records = listHookTrustRecords(state.parsed.executableHandlers, state.trust, { platform: process.platform });\n\tconst index = records.findIndex((record) => record.id === id);\n\tif (index < 0) return undefined;\n\tconst handler = state.parsed.executableHandlers[index];\n\tconst record = records[index];\n\tif (handler === undefined || record === undefined) return undefined;\n\treturn { handler, record };\n}\n\nfunction entryForAction(action: \"trust\" | \"disable\" | \"enable\", pair: HookRecordPair): HookTrustEntry {\n\tif (action === \"trust\") {\n\t\treturn createHookTrustEntry(pair.handler, { platform: process.platform });\n\t}\n\treturn {\n\t\tenabled: action === \"enable\",\n\t\t...(pair.record.entry?.trustedHash === undefined ? {} : { trustedHash: pair.record.entry.trustedHash }),\n\t\tscope: pair.record.scope,\n\t\tsourcePath: pair.record.sourcePath,\n\t\t...(pair.record.matcher === undefined ? {} : { matcher: pair.record.matcher }),\n\t\tcommandPreview: pair.record.commandPreview,\n\t\tupdatedAt: new Date().toISOString(),\n\t};\n}\n\nfunction pastTenseAction(action: \"trust\" | \"disable\" | \"enable\"): string {\n\tswitch (action) {\n\t\tcase \"trust\":\n\t\t\treturn \"Trusted\";\n\t\tcase \"disable\":\n\t\t\treturn \"Disabled\";\n\t\tcase \"enable\":\n\t\t\treturn \"Enabled\";\n\t}\n}\n\nexport function formatHookStatus(state: HookCommandRuntimeState): string {\n\tconst parsed = state.parsed;\n\tconst counts = eventCounts(parsed);\n\tconst summary =\n\t\tcounts.size === 0\n\t\t\t? \"hooks: no executable hooks\"\n\t\t\t: `hooks: ${parsed.executableHandlers.length} executable hooks (${Array.from(counts.entries())\n\t\t\t\t\t.map(([event, count]) => `${event}:${count}`)\n\t\t\t\t\t.join(\", \")})`;\n\tconst records = listHookTrustRecords(parsed.executableHandlers, state.trust, { platform: process.platform });\n\tif (records.length === 0) {\n\t\treturn summary;\n\t}\n\treturn [summary, ...records.map((record, index) => formatHookRecord(parsed.executableHandlers[index], record))]\n\t\t.filter((line) => line.length > 0)\n\t\t.join(\"\\n\");\n}\n\nfunction eventCounts(parsed: ParsedHookConfig): Map<SupportedHookEvent, number> {\n\tconst counts = new Map<SupportedHookEvent, number>();\n\tfor (const handler of parsed.executableHandlers) {\n\t\tcounts.set(handler.event, (counts.get(handler.event) ?? 0) + 1);\n\t}\n\treturn counts;\n}\n\nfunction formatHookRecord(handler: ExecutableHookHandler | undefined, record: HookTrustRecord): string {\n\tif (handler === undefined) return \"\";\n\tconst parts = [\n\t\t`- ${record.id}`,\n\t\thandler.event,\n\t\t`source:${record.scope}`,\n\t\t`status:${record.trusted ? \"trusted\" : \"untrusted\"}`,\n\t\t`disabled:${record.enabled ? \"false\" : \"true\"}`,\n\t];\n\tif (record.matcher !== undefined) {\n\t\tparts.push(`matcher:${sanitizeDisplayText(record.matcher)}`);\n\t}\n\tif (handler.config.statusMessage !== undefined) {\n\t\tparts.push(`statusMessage:${sanitizeDisplayText(handler.config.statusMessage)}`);\n\t}\n\tparts.push(`command:${sanitizeDisplayText(record.commandPreview)}`);\n\treturn parts.join(\" \");\n}\n\nexport function formatHookDiagnostics(diagnostics: readonly HookDiagnostic[]): string {\n\tif (diagnostics.length === 0) {\n\t\treturn \"hooks diagnostics: none\";\n\t}\n\tconst visible = diagnostics.slice(0, DIAGNOSTIC_DISPLAY_LIMIT);\n\tconst lines = visible.map(\n\t\t(diagnostic) =>\n\t\t\t`${diagnostic.severity}: ${diagnostic.code} ${sanitizeDisplayText(diagnostic.source.sourcePath)} ${sanitizeDisplayText(\n\t\t\t\tdiagnostic.path,\n\t\t\t)} ${sanitizeDisplayText(diagnostic.message)}`,\n\t);\n\tconst remaining = diagnostics.length - visible.length;\n\tif (remaining > 0) {\n\t\tlines.push(`... ${remaining} more diagnostics`);\n\t}\n\treturn [`hooks diagnostics: ${diagnostics.length} diagnostics`, ...lines].join(\"\\n\");\n}\n\nfunction sanitizeDisplayText(value: string): string {\n\treturn redactHookTokenValues(\n\t\tvalue\n\t\t\t.replace(\n\t\t\t\t/(--(?:api-key|apikey|auth|authorization|password|secret|token)=)(?:\"[^\"]*\"|'[^']*'|\\S+)/gi,\n\t\t\t\t\"$1[redacted]\",\n\t\t\t)\n\t\t\t.replace(\n\t\t\t\t/(--(?:api-key|apikey|auth|authorization|password|secret|token)\\s+)(?:\"[^\"]*\"|'[^']*'|\\S+)/gi,\n\t\t\t\t\"$1[redacted]\",\n\t\t\t)\n\t\t\t.replace(/\\b(Bearer\\s+)[A-Za-z0-9._~+/=-]+/gi, \"$1[redacted]\")\n\t\t\t.replace(/\\b(?:sk|pk|rk|glpat)-[A-Za-z0-9._-]+/g, \"[redacted]\"),\n\t\t\"[redacted]\",\n\t);\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ParsedHookConfig } from "./types.ts";
|
|
2
|
+
export type HookConfigFileSystem = {
|
|
3
|
+
readonly readTextFile: (path: string) => string | undefined;
|
|
4
|
+
};
|
|
5
|
+
export type HookConfigLoaderOptions = {
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly agentDir: string;
|
|
8
|
+
readonly fileSystem: HookConfigFileSystem;
|
|
9
|
+
readonly globalSettingsHooks?: unknown;
|
|
10
|
+
readonly projectSettingsHooks?: unknown;
|
|
11
|
+
readonly globalHooksPath?: string;
|
|
12
|
+
readonly projectHooksPath?: string;
|
|
13
|
+
readonly globalHookSourcePaths?: readonly string[];
|
|
14
|
+
readonly projectHookSourcePaths?: readonly string[];
|
|
15
|
+
readonly preSessionHookSourcePaths?: readonly string[];
|
|
16
|
+
readonly runtimeHookSourcePaths?: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
export declare function loadHookConfigSources(options: HookConfigLoaderOptions): ParsedHookConfig;
|
|
19
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/config-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsC,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvF,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC5D,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;IAC1C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnD,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpD,QAAQ,CAAC,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvD,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD,CAAC;AAsBF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CAyBxF"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { diagnostic } from "./diagnostics.js";
|
|
2
|
+
import { parseHookConfig } from "./schema.js";
|
|
3
|
+
export function loadHookConfigSources(options) {
|
|
4
|
+
const executableHandlers = [];
|
|
5
|
+
const diagnostics = [];
|
|
6
|
+
let displayOrder = 0;
|
|
7
|
+
for (const candidate of createSourceCandidates(options)) {
|
|
8
|
+
const source = {
|
|
9
|
+
discoveredAt: candidate.discoveredAt,
|
|
10
|
+
displayOrder,
|
|
11
|
+
scope: candidate.scope,
|
|
12
|
+
sourcePath: candidate.sourcePath,
|
|
13
|
+
};
|
|
14
|
+
const loaded = loadCandidate(candidate, source, options.fileSystem);
|
|
15
|
+
if (!loaded) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
displayOrder += 1;
|
|
19
|
+
executableHandlers.push(...loaded.executableHandlers);
|
|
20
|
+
diagnostics.push(...loaded.diagnostics);
|
|
21
|
+
if (source.discoveredAt === "runtime") {
|
|
22
|
+
diagnostics.push(...runtimeSessionStartDiagnostics(loaded.executableHandlers, source));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return { executableHandlers, diagnostics };
|
|
26
|
+
}
|
|
27
|
+
function createSourceCandidates(options) {
|
|
28
|
+
const candidates = [];
|
|
29
|
+
if (options.globalSettingsHooks !== undefined) {
|
|
30
|
+
candidates.push({
|
|
31
|
+
discoveredAt: "pre-session",
|
|
32
|
+
hooks: options.globalSettingsHooks,
|
|
33
|
+
kind: "inline",
|
|
34
|
+
scope: "global",
|
|
35
|
+
sourcePath: "<global-settings-hooks>",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
candidates.push(fileSource("global", options.globalHooksPath ?? `${options.agentDir}/hooks.json`, "pre-session"));
|
|
39
|
+
candidates.push(...fileSources("global", options.globalHookSourcePaths, "pre-session"));
|
|
40
|
+
if (options.projectSettingsHooks !== undefined) {
|
|
41
|
+
candidates.push({
|
|
42
|
+
discoveredAt: "pre-session",
|
|
43
|
+
hooks: options.projectSettingsHooks,
|
|
44
|
+
kind: "inline",
|
|
45
|
+
scope: "project",
|
|
46
|
+
sourcePath: "<project-settings-hooks>",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
candidates.push(fileSource("project", options.projectHooksPath ?? `${options.cwd}/.senpi/hooks.json`, "pre-session"));
|
|
50
|
+
candidates.push(...fileSources("project", options.projectHookSourcePaths, "pre-session"));
|
|
51
|
+
candidates.push(...fileSources("plugin", options.preSessionHookSourcePaths, "pre-session"));
|
|
52
|
+
candidates.push(...fileSources("runtime", options.runtimeHookSourcePaths, "runtime"));
|
|
53
|
+
return candidates;
|
|
54
|
+
}
|
|
55
|
+
function fileSources(scope, paths, discoveredAt) {
|
|
56
|
+
if (!paths) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
return paths.map((path) => fileSource(scope, path, discoveredAt));
|
|
60
|
+
}
|
|
61
|
+
function fileSource(scope, sourcePath, discoveredAt) {
|
|
62
|
+
return { discoveredAt, kind: "file", scope, sourcePath };
|
|
63
|
+
}
|
|
64
|
+
function loadCandidate(candidate, source, fileSystem) {
|
|
65
|
+
if (candidate.kind === "inline") {
|
|
66
|
+
return parseHookConfig({ hooks: candidate.hooks }, source);
|
|
67
|
+
}
|
|
68
|
+
const text = readSourceText(candidate.sourcePath, source, fileSystem);
|
|
69
|
+
if (text === undefined) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
if (typeof text !== "string") {
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(text);
|
|
77
|
+
return parseHookConfig(parsed, source);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const message = error instanceof Error ? error.message : "Unknown JSON parse failure.";
|
|
81
|
+
return {
|
|
82
|
+
diagnostics: [
|
|
83
|
+
diagnostic({
|
|
84
|
+
code: "invalid_root",
|
|
85
|
+
message: `Hook source JSON is malformed: ${message}`,
|
|
86
|
+
path: "$",
|
|
87
|
+
}, source),
|
|
88
|
+
],
|
|
89
|
+
executableHandlers: [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function readSourceText(sourcePath, source, fileSystem) {
|
|
94
|
+
try {
|
|
95
|
+
return fileSystem.readTextFile(sourcePath);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const message = error instanceof Error ? error.message : "Unknown file read failure.";
|
|
99
|
+
return {
|
|
100
|
+
diagnostics: [
|
|
101
|
+
diagnostic({
|
|
102
|
+
code: "invalid_root",
|
|
103
|
+
message: `Hook source could not be read: ${message}`,
|
|
104
|
+
path: "$",
|
|
105
|
+
}, source),
|
|
106
|
+
],
|
|
107
|
+
executableHandlers: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function runtimeSessionStartDiagnostics(handlers, source) {
|
|
112
|
+
if (!handlers.some((handler) => handler.event === "SessionStart")) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
return [
|
|
116
|
+
diagnostic({
|
|
117
|
+
code: "unsupported_event",
|
|
118
|
+
event: "SessionStart",
|
|
119
|
+
message: "Runtime SessionStart hooks are loaded for reload or the next session only.",
|
|
120
|
+
path: "hooks.SessionStart",
|
|
121
|
+
severity: "warning",
|
|
122
|
+
}, source),
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/config-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { diagnostic } from \"./diagnostics.ts\";\nimport { parseHookConfig } from \"./schema.ts\";\nimport type { HookDiagnostic, HookSourceMetadata, ParsedHookConfig } from \"./types.ts\";\n\nexport type HookConfigFileSystem = {\n\treadonly readTextFile: (path: string) => string | undefined;\n};\n\nexport type HookConfigLoaderOptions = {\n\treadonly cwd: string;\n\treadonly agentDir: string;\n\treadonly fileSystem: HookConfigFileSystem;\n\treadonly globalSettingsHooks?: unknown;\n\treadonly projectSettingsHooks?: unknown;\n\treadonly globalHooksPath?: string;\n\treadonly projectHooksPath?: string;\n\treadonly globalHookSourcePaths?: readonly string[];\n\treadonly projectHookSourcePaths?: readonly string[];\n\treadonly preSessionHookSourcePaths?: readonly string[];\n\treadonly runtimeHookSourcePaths?: readonly string[];\n};\n\ntype SourceScope = HookSourceMetadata[\"scope\"];\ntype SourceTiming = HookSourceMetadata[\"discoveredAt\"];\n\ntype InlineSource = {\n\treadonly kind: \"inline\";\n\treadonly hooks: unknown;\n\treadonly scope: SourceScope;\n\treadonly sourcePath: string;\n\treadonly discoveredAt: SourceTiming;\n};\n\ntype FileSource = {\n\treadonly kind: \"file\";\n\treadonly scope: SourceScope;\n\treadonly sourcePath: string;\n\treadonly discoveredAt: SourceTiming;\n};\n\ntype SourceCandidate = InlineSource | FileSource;\n\nexport function loadHookConfigSources(options: HookConfigLoaderOptions): ParsedHookConfig {\n\tconst executableHandlers: ParsedHookConfig[\"executableHandlers\"][number][] = [];\n\tconst diagnostics: HookDiagnostic[] = [];\n\tlet displayOrder = 0;\n\n\tfor (const candidate of createSourceCandidates(options)) {\n\t\tconst source: HookSourceMetadata = {\n\t\t\tdiscoveredAt: candidate.discoveredAt,\n\t\t\tdisplayOrder,\n\t\t\tscope: candidate.scope,\n\t\t\tsourcePath: candidate.sourcePath,\n\t\t};\n\t\tconst loaded = loadCandidate(candidate, source, options.fileSystem);\n\t\tif (!loaded) {\n\t\t\tcontinue;\n\t\t}\n\t\tdisplayOrder += 1;\n\t\texecutableHandlers.push(...loaded.executableHandlers);\n\t\tdiagnostics.push(...loaded.diagnostics);\n\t\tif (source.discoveredAt === \"runtime\") {\n\t\t\tdiagnostics.push(...runtimeSessionStartDiagnostics(loaded.executableHandlers, source));\n\t\t}\n\t}\n\n\treturn { executableHandlers, diagnostics };\n}\n\nfunction createSourceCandidates(options: HookConfigLoaderOptions): SourceCandidate[] {\n\tconst candidates: SourceCandidate[] = [];\n\tif (options.globalSettingsHooks !== undefined) {\n\t\tcandidates.push({\n\t\t\tdiscoveredAt: \"pre-session\",\n\t\t\thooks: options.globalSettingsHooks,\n\t\t\tkind: \"inline\",\n\t\t\tscope: \"global\",\n\t\t\tsourcePath: \"<global-settings-hooks>\",\n\t\t});\n\t}\n\tcandidates.push(fileSource(\"global\", options.globalHooksPath ?? `${options.agentDir}/hooks.json`, \"pre-session\"));\n\tcandidates.push(...fileSources(\"global\", options.globalHookSourcePaths, \"pre-session\"));\n\tif (options.projectSettingsHooks !== undefined) {\n\t\tcandidates.push({\n\t\t\tdiscoveredAt: \"pre-session\",\n\t\t\thooks: options.projectSettingsHooks,\n\t\t\tkind: \"inline\",\n\t\t\tscope: \"project\",\n\t\t\tsourcePath: \"<project-settings-hooks>\",\n\t\t});\n\t}\n\tcandidates.push(\n\t\tfileSource(\"project\", options.projectHooksPath ?? `${options.cwd}/.senpi/hooks.json`, \"pre-session\"),\n\t);\n\tcandidates.push(...fileSources(\"project\", options.projectHookSourcePaths, \"pre-session\"));\n\tcandidates.push(...fileSources(\"plugin\", options.preSessionHookSourcePaths, \"pre-session\"));\n\tcandidates.push(...fileSources(\"runtime\", options.runtimeHookSourcePaths, \"runtime\"));\n\treturn candidates;\n}\n\nfunction fileSources(\n\tscope: SourceScope,\n\tpaths: readonly string[] | undefined,\n\tdiscoveredAt: SourceTiming,\n): SourceCandidate[] {\n\tif (!paths) {\n\t\treturn [];\n\t}\n\treturn paths.map((path) => fileSource(scope, path, discoveredAt));\n}\n\nfunction fileSource(scope: SourceScope, sourcePath: string, discoveredAt: SourceTiming): FileSource {\n\treturn { discoveredAt, kind: \"file\", scope, sourcePath };\n}\n\nfunction loadCandidate(\n\tcandidate: SourceCandidate,\n\tsource: HookSourceMetadata,\n\tfileSystem: HookConfigFileSystem,\n): ParsedHookConfig | undefined {\n\tif (candidate.kind === \"inline\") {\n\t\treturn parseHookConfig({ hooks: candidate.hooks }, source);\n\t}\n\n\tconst text = readSourceText(candidate.sourcePath, source, fileSystem);\n\tif (text === undefined) {\n\t\treturn undefined;\n\t}\n\tif (typeof text !== \"string\") {\n\t\treturn text;\n\t}\n\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(text);\n\t\treturn parseHookConfig(parsed, source);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"Unknown JSON parse failure.\";\n\t\treturn {\n\t\t\tdiagnostics: [\n\t\t\t\tdiagnostic(\n\t\t\t\t\t{\n\t\t\t\t\t\tcode: \"invalid_root\",\n\t\t\t\t\t\tmessage: `Hook source JSON is malformed: ${message}`,\n\t\t\t\t\t\tpath: \"$\",\n\t\t\t\t\t},\n\t\t\t\t\tsource,\n\t\t\t\t),\n\t\t\t],\n\t\t\texecutableHandlers: [],\n\t\t};\n\t}\n}\n\nfunction readSourceText(\n\tsourcePath: string,\n\tsource: HookSourceMetadata,\n\tfileSystem: HookConfigFileSystem,\n): string | undefined | ParsedHookConfig {\n\ttry {\n\t\treturn fileSystem.readTextFile(sourcePath);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"Unknown file read failure.\";\n\t\treturn {\n\t\t\tdiagnostics: [\n\t\t\t\tdiagnostic(\n\t\t\t\t\t{\n\t\t\t\t\t\tcode: \"invalid_root\",\n\t\t\t\t\t\tmessage: `Hook source could not be read: ${message}`,\n\t\t\t\t\t\tpath: \"$\",\n\t\t\t\t\t},\n\t\t\t\t\tsource,\n\t\t\t\t),\n\t\t\t],\n\t\t\texecutableHandlers: [],\n\t\t};\n\t}\n}\n\nfunction runtimeSessionStartDiagnostics(\n\thandlers: ParsedHookConfig[\"executableHandlers\"],\n\tsource: HookSourceMetadata,\n): HookDiagnostic[] {\n\tif (!handlers.some((handler) => handler.event === \"SessionStart\")) {\n\t\treturn [];\n\t}\n\treturn [\n\t\tdiagnostic(\n\t\t\t{\n\t\t\t\tcode: \"unsupported_event\",\n\t\t\t\tevent: \"SessionStart\",\n\t\t\t\tmessage: \"Runtime SessionStart hooks are loaded for reload or the next session only.\",\n\t\t\t\tpath: \"hooks.SessionStart\",\n\t\t\t\tseverity: \"warning\",\n\t\t\t},\n\t\t\tsource,\n\t\t),\n\t];\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HookDiagnostic, HookDiagnosticCode, HookSourceMetadata } from "./types.ts";
|
|
2
|
+
export type DiagnosticDraft = {
|
|
3
|
+
readonly code: HookDiagnosticCode;
|
|
4
|
+
readonly message: string;
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly event?: string;
|
|
7
|
+
readonly severity?: "error" | "warning";
|
|
8
|
+
};
|
|
9
|
+
export declare function diagnostic(draft: DiagnosticDraft, source: HookSourceMetadata): HookDiagnostic;
|
|
10
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/hooks/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEzF,MAAM,MAAM,eAAe,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,kBAAkB,GAAG,cAAc,CAY7F"}
|