@agent-guardrails/opencode-shell-warden-hook 0.2.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-guardrails/opencode-shell-warden-hook",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "OpenCode V2 hook plugin: forwards shell permission decisions to the shell-warden engine over NDJSON JSON-RPC stdio",
5
5
  "type": "module",
6
6
  "repository": {
package/src/apply.ts CHANGED
@@ -27,24 +27,29 @@ export function cacheKey(action: string, resources: readonly string[], cwd: stri
27
27
  .digest("hex")
28
28
  }
29
29
 
30
- /** Letter per 02-letter-verdict.md §2.2 — exactly the schema fields, no extras. */
30
+ /** Letter per 02-letter-verdict.md §2.2 — exactly the schema fields, no extras.
31
+ * `projectDir` is required and non-empty: the engine invalidates every letter whose
32
+ * commands[].cwd is empty ("letter commands[0].cwd must be 1..4096 chars"), and the
33
+ * ask payload carries no per-command cwd — the permission request's directory is the
34
+ * only cwd the shim has. Callers without a directory must degrade before calling. */
31
35
  export function buildLetter(args: {
32
36
  data: AskData
33
37
  invocationId: string
34
- projectDir?: string
38
+ projectDir: string
35
39
  agent?: string
36
40
  }): Letter {
37
41
  const { data, invocationId, projectDir, agent } = args
42
+ if (!projectDir) throw new Error("buildLetter: projectDir is required — the engine rejects letters with an empty commands[].cwd")
38
43
  return {
39
44
  version: 1,
40
45
  invocation: {
41
46
  id: invocationId,
42
47
  sessionID: data.sessionID ?? "",
43
48
  ...(agent !== undefined ? { agent } : {}),
44
- ...(projectDir !== undefined ? { projectDir } : {}),
49
+ projectDir,
45
50
  },
46
51
  action: data.action ?? "",
47
- commands: (data.resources ?? []).map((raw, index) => ({ index, raw, cwd: "" })),
52
+ commands: (data.resources ?? []).map((raw, index) => ({ index, raw, cwd: projectDir })),
48
53
  }
49
54
  }
50
55
 
package/src/index.ts CHANGED
@@ -80,8 +80,17 @@ export default Plugin.define({
80
80
  try {
81
81
  if (!(cfg.actionsAll || cfg.actions.includes(d.action))) continue
82
82
  const invocationId = invocationIdOf(d.source, d.sessionID, d.action)
83
- const projectDir = myLocation || undefined
84
- const key = cacheKey(d.action, d.resources, projectDir ?? "")
83
+ // The ask payload carries no per-command cwd; the permission event's
84
+ // location is the only directory the shim has. Letters with an empty
85
+ // commands[].cwd are rejected by the engine, so without one we degrade —
86
+ // never spawn a letter the engine would refuse.
87
+ const projectDir = loc || myLocation || ""
88
+ if (!projectDir) {
89
+ log("warn", `no project directory for ${invocationId} — degrade (${cfg.onCheckerError})`)
90
+ if (cfg.onCheckerError === "deny") await reply(d.sessionID, d.id, "reject", undefined).catch(() => {})
91
+ continue
92
+ }
93
+ const key = cacheKey(d.action, d.resources, projectDir)
85
94
 
86
95
  if (cfg.cache.enabled) {
87
96
  const cached = cache.get(key)