plugnz 0.0.22 → 0.0.24
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/dist/hook-runtime.mjs +52 -8
- package/dist/plugnz.mjs +730 -614
- package/package.json +1 -1
package/dist/hook-runtime.mjs
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
// src/hooks.ts
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
4
4
|
import { isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
var HOOK_EVENTS = ["session.start", "turn.stop", "tool.before", "tool.after"];
|
|
6
6
|
var HOOK_CONFIG_FILE = "plugnz.hooks.json";
|
|
7
7
|
var DEFAULT_HOOK_TIMEOUT_MS = 1e4;
|
|
8
8
|
var MAX_HOOK_TIMEOUT_MS = 60000;
|
|
9
9
|
var MAX_HOOK_OUTPUT_BYTES = 1048576;
|
|
10
|
+
var HOOK_EFFECTS = ["context", "continue"];
|
|
11
|
+
function hookEffectGap(required, supported) {
|
|
12
|
+
for (const effect of HOOK_EFFECTS) {
|
|
13
|
+
if (required?.[effect] !== true)
|
|
14
|
+
continue;
|
|
15
|
+
if (supported?.[effect] !== true)
|
|
16
|
+
return { effect, status: supported?.[effect] === false ? "unsupported" : "unverified" };
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
10
20
|
function record(value, label) {
|
|
11
21
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
12
22
|
throw new Error(label + " must be an object");
|
|
@@ -31,8 +41,9 @@ function isHookEvent(value) {
|
|
|
31
41
|
function validateHookConfig(value) {
|
|
32
42
|
const root = record(value, "Hook configuration");
|
|
33
43
|
keys(root, ["version", "hooks"], "Hook configuration");
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
const version = root["version"];
|
|
45
|
+
if (version !== 1 && version !== 2)
|
|
46
|
+
throw new Error("Hook configuration version must be 1 or 2");
|
|
36
47
|
const sourceHooks = record(root["hooks"], "hooks");
|
|
37
48
|
keys(sourceHooks, HOOK_EVENTS, "hooks");
|
|
38
49
|
const hooks = {};
|
|
@@ -45,7 +56,22 @@ function validateHookConfig(value) {
|
|
|
45
56
|
hooks[event] = handlers.map((value, index) => {
|
|
46
57
|
const label = event + "[" + index + "]";
|
|
47
58
|
const handler = record(value, label);
|
|
48
|
-
keys(handler, ["command", "tools", "timeoutMs"], label);
|
|
59
|
+
keys(handler, version === 1 ? ["command", "tools", "timeoutMs"] : ["command", "tools", "timeoutMs", "requires"], label);
|
|
60
|
+
let requires;
|
|
61
|
+
if (version === 2) {
|
|
62
|
+
const effects = handler["requires"] === undefined ? {} : record(handler["requires"], label + ".requires");
|
|
63
|
+
keys(effects, HOOK_EFFECTS, label + ".requires");
|
|
64
|
+
for (const effect of HOOK_EFFECTS) {
|
|
65
|
+
if (effects[effect] !== undefined && typeof effects[effect] !== "boolean")
|
|
66
|
+
throw new Error(label + ".requires." + effect + " must be boolean");
|
|
67
|
+
}
|
|
68
|
+
requires = { ...effects };
|
|
69
|
+
if (requires.continue === true && event !== "turn.stop")
|
|
70
|
+
throw new Error(label + ": continue is only available at turn.stop");
|
|
71
|
+
if (event === "turn.stop" && requires.context === true !== (requires.continue === true)) {
|
|
72
|
+
throw new Error(label + ": turn.stop requires context:true and continue:true together");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
49
75
|
const command = handler["command"];
|
|
50
76
|
if (!Array.isArray(command) || command.length === 0)
|
|
51
77
|
throw new Error(label + ".command must be a nonempty argv array");
|
|
@@ -63,15 +89,25 @@ function validateHookConfig(value) {
|
|
|
63
89
|
if (timeout !== undefined && (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout < 1 || timeout > MAX_HOOK_TIMEOUT_MS)) {
|
|
64
90
|
throw new Error(label + ".timeoutMs must be an integer from 1 to " + MAX_HOOK_TIMEOUT_MS);
|
|
65
91
|
}
|
|
66
|
-
return { command: argv, ...tools === undefined ? {} : { tools }, ...timeout === undefined ? {} : { timeoutMs: timeout } };
|
|
92
|
+
return { command: argv, ...tools === undefined ? {} : { tools }, ...timeout === undefined ? {} : { timeoutMs: timeout }, ...requires === undefined ? {} : { requires } };
|
|
67
93
|
});
|
|
68
94
|
}
|
|
69
|
-
return { version
|
|
95
|
+
return { version, hooks };
|
|
70
96
|
}
|
|
71
97
|
function readHookConfig(pluginRoot) {
|
|
72
98
|
const path = join(pluginRoot, HOOK_CONFIG_FILE);
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
let stat;
|
|
100
|
+
try {
|
|
101
|
+
stat = lstatSync(path);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.code === "ENOENT")
|
|
104
|
+
return;
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
if (stat.isSymbolicLink())
|
|
108
|
+
throw new Error("Hook source symlinks are unsupported: " + path);
|
|
109
|
+
if (!stat.isFile())
|
|
110
|
+
throw new Error("Hook manifest must be a regular file: " + path);
|
|
75
111
|
let value;
|
|
76
112
|
try {
|
|
77
113
|
value = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -179,6 +215,12 @@ async function runHooks(configValue, inputValue, pluginRoot) {
|
|
|
179
215
|
}
|
|
180
216
|
}
|
|
181
217
|
const result = validateHookResult(raw, input);
|
|
218
|
+
if (config.version === 2) {
|
|
219
|
+
if (result.context !== undefined && handler.requires?.context !== true)
|
|
220
|
+
throw new Error("undeclared context effect; declare requires.context:true");
|
|
221
|
+
if (result.continue === true && handler.requires?.continue !== true)
|
|
222
|
+
throw new Error("undeclared continuation effect; declare requires.continue:true");
|
|
223
|
+
}
|
|
182
224
|
if (result.context !== undefined)
|
|
183
225
|
contexts.push(result.context);
|
|
184
226
|
if (result.continue !== undefined)
|
|
@@ -277,9 +319,11 @@ function executeHandler(handler, input, pluginRoot) {
|
|
|
277
319
|
export {
|
|
278
320
|
DEFAULT_HOOK_TIMEOUT_MS,
|
|
279
321
|
HOOK_CONFIG_FILE,
|
|
322
|
+
HOOK_EFFECTS,
|
|
280
323
|
HOOK_EVENTS,
|
|
281
324
|
MAX_HOOK_OUTPUT_BYTES,
|
|
282
325
|
MAX_HOOK_TIMEOUT_MS,
|
|
326
|
+
hookEffectGap,
|
|
283
327
|
readHookConfig,
|
|
284
328
|
runHooks,
|
|
285
329
|
validateHookConfig,
|