@scopebond/hook 0.15.2 → 0.16.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 +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +31 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/managed.d.ts +16 -1
- package/dist/managed.d.ts.map +1 -1
- package/dist/managed.js +29 -6
- package/dist/managed.js.map +1 -1
- package/dist/override.d.ts +51 -0
- package/dist/override.d.ts.map +1 -0
- package/dist/override.js +193 -0
- package/dist/override.js.map +1 -0
- package/dist/runtime.d.ts +9 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +12 -3
- package/dist/runtime.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -266,6 +266,15 @@ patterns: the hook compiles its choices with the same compiler as `rules apply`.
|
|
|
266
266
|
- **What the workspace cannot change.** Protection of Scopebond's own settings and of the
|
|
267
267
|
agents' hook settings, the machine key policy, fail-closed handling of anything unreadable,
|
|
268
268
|
and this computer's own opt-ins (`allowed_roots`, `protect_remote_database`).
|
|
269
|
+
- **Overrides.** The workspace can set a rule to *Block, user may override* and say who may
|
|
270
|
+
override. A matching action is then blocked until the person at the computer allows it once,
|
|
271
|
+
with a reason, in the Scopebond Agent's window (`@scopebond/agent`): the hook asks the running
|
|
272
|
+
agent and waits up to 45 seconds (20 seconds for Codex and Cursor), then blocks. The receipt
|
|
273
|
+
records the override, the reason's digest and the rule; the workspace adds who and why. Where the
|
|
274
|
+
workspace allows it and the window is not available, Claude Code's own prompt is offered instead,
|
|
275
|
+
but only in a permission mode where Claude Code really asks; that prompt takes no reason and is
|
|
276
|
+
recorded as *offered*. Scopebond's own protection, rules set to Block and the kill switch are
|
|
277
|
+
never overridable, and the workspace's daily limit holds.
|
|
269
278
|
- **Going back.** While the workspace sets the rules, `rules` edits and `policy load` are
|
|
270
279
|
refused here. If the connection is revoked, or the workspace stops setting rules for this
|
|
271
280
|
computer, the hook recompiles `policy.json` from `rules.json`: a computer is never left
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA+rBA,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,IAAI,CAS7F"}
|
package/dist/cli.js
CHANGED
|
@@ -45,6 +45,7 @@ import { connectCloud, ingestUrl, loadConnection } from "./cloud.js";
|
|
|
45
45
|
import { recoverEarlierReceipts } from "./recover.js";
|
|
46
46
|
import { loadPolicyExport, policyBuilds } from "./policy-load.js";
|
|
47
47
|
import { isManaged, readMeta, MANAGED_DOC_FILE } from "./managed.js";
|
|
48
|
+
import { createOverrideHandler, overrideHint } from "./override.js";
|
|
48
49
|
import { syncIfDue, syncPolicy } from "./policy-sync.js";
|
|
49
50
|
import { loadBudgetExport } from "./budget-load.js";
|
|
50
51
|
import { compile, defaultRules, describeRules, loadRules, saveRules, rulesPath, pathRuleFor } from "./rules.js";
|
|
@@ -129,6 +130,17 @@ function readStdin() {
|
|
|
129
130
|
return "";
|
|
130
131
|
}
|
|
131
132
|
}
|
|
133
|
+
/** How long a hook waits for a person in the Scopebond window: well inside each agent's hook timeout (Claude Code 60 s by
|
|
134
|
+
* default; the Codex entry Scopebond writes says 30 s), so the wait ends as a block, never as a timed-out hook. */
|
|
135
|
+
const OVERRIDE_WAIT_MS = { claude: 45_000, codex: 20_000, cursor: 20_000 };
|
|
136
|
+
/** Warn mode: Claude Code shows the person its own prompt for this action (only where the workspace allows it and Claude Code
|
|
137
|
+
* really asks; see override.ts). */
|
|
138
|
+
function askClaude(reason) {
|
|
139
|
+
process.stdout.write(JSON.stringify({
|
|
140
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: reason },
|
|
141
|
+
}) + "\n");
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
132
144
|
function denyClaude(reason) {
|
|
133
145
|
process.stdout.write(JSON.stringify({
|
|
134
146
|
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason },
|
|
@@ -218,7 +230,11 @@ async function runPreToolUse(mapper, deny = denyClaude, raw, harness = "claude")
|
|
|
218
230
|
try {
|
|
219
231
|
const cwd = input?.cwd ? String(input.cwd) : process.cwd();
|
|
220
232
|
const dir = resolveConfigDir(cwd);
|
|
221
|
-
|
|
233
|
+
const permissionMode = typeof input.permission_mode === "string" ? input.permission_mode : null;
|
|
234
|
+
runtime = createHookRuntime({ ...runtimePaths(dir, cwd), override: (agentKid) => {
|
|
235
|
+
const made = createOverrideHandler({ dir, home: userHome(), agentKid, harness, permissionMode, waitMs: OVERRIDE_WAIT_MS[harness] });
|
|
236
|
+
return made ? { handler: made.handler, hint: () => overrideHint(made.note()) } : null;
|
|
237
|
+
} });
|
|
222
238
|
useDigestKey(loadOrCreateDigestKey(dir));
|
|
223
239
|
const decision = await runtime.evaluate([...fillPushBranch(mapper(input), currentBranch(cwd)), ...databaseGuard(dir, cwd, input)], { groupKey: callId(input) });
|
|
224
240
|
const observer = recordObservations(dir, cwd, input, decision, harness);
|
|
@@ -231,6 +247,12 @@ async function runPreToolUse(mapper, deny = denyClaude, raw, harness = "claude")
|
|
|
231
247
|
runtime = undefined;
|
|
232
248
|
if (decision.decision === "deny")
|
|
233
249
|
deny(decision.reason);
|
|
250
|
+
// Only Claude Code is ever offered "ask" (override.ts); any other harness treats it as a denial.
|
|
251
|
+
if (decision.decision === "ask") {
|
|
252
|
+
if (harness === "claude")
|
|
253
|
+
askClaude(decision.reason);
|
|
254
|
+
deny(decision.reason);
|
|
255
|
+
}
|
|
234
256
|
// Stay silent on allow/not_evaluated so the coding agent's normal permission
|
|
235
257
|
// flow remains in charge. Scopebond blocks; it never silently approves.
|
|
236
258
|
process.exit(0);
|
|
@@ -329,9 +351,15 @@ async function runCursor() {
|
|
|
329
351
|
try {
|
|
330
352
|
const cwd = input?.cwd ? String(input.cwd) : process.cwd();
|
|
331
353
|
const dir = resolveConfigDir(cwd);
|
|
332
|
-
|
|
333
|
-
useDigestKey(loadOrCreateDigestKey(dir));
|
|
354
|
+
const permissionMode = typeof input.permission_mode === "string" ? input.permission_mode : null;
|
|
334
355
|
const mapped = fillPushBranch(mapCursorEvent(event, input), currentBranch(cwd));
|
|
356
|
+
// An edit Cursor reports after saving it cannot be overridden: it already happened.
|
|
357
|
+
const overridable = !mapped.some((m) => m.postHoc);
|
|
358
|
+
runtime = createHookRuntime({ ...runtimePaths(dir, cwd), override: (agentKid) => {
|
|
359
|
+
const made = overridable ? createOverrideHandler({ dir, home: userHome(), agentKid, harness: "cursor", permissionMode, waitMs: OVERRIDE_WAIT_MS.cursor }) : null;
|
|
360
|
+
return made ? { handler: made.handler, hint: () => overrideHint(made.note()) } : null;
|
|
361
|
+
} });
|
|
362
|
+
useDigestKey(loadOrCreateDigestKey(dir));
|
|
335
363
|
const decision = await runtime.evaluate(mapped, { groupKey: callId(input) });
|
|
336
364
|
const observer = recordObservations(dir, cwd, input, decision, "cursor");
|
|
337
365
|
await Promise.all([runtime.flush(), observer?.flush() ?? Promise.resolve(), syncIfDue(dir, () => syncOptionsFor(dir))]);
|