@dsh-cc/plugin-dsh-cc-shunt 0.8.1-rc.1 → 0.8.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/.claude-plugin/plugin.json +1 -1
- package/README.i18n.yaml +2 -2
- package/README.md +18 -0
- package/README.zh.md +8 -0
- package/hooks/check-bash-read.mjs +4 -0
- package/hooks/check-file-size.mjs +30 -1
- package/package.json +1 -1
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm check:readme --write
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: af2dde8faba4b786c213fc515d15182f81c1809a
|
|
6
|
+
README.zh.md: dd7e01ae12856243617fdd46b1ad69231f2f52f3
|
package/README.md
CHANGED
|
@@ -43,6 +43,24 @@ alias is unconfigured, the workers silently inherit the parent's model route
|
|
|
43
43
|
— everything works, but you get **zero token savings**. Configure the haiku
|
|
44
44
|
alias for actual savings.
|
|
45
45
|
|
|
46
|
+
## Subagent exemption
|
|
47
|
+
|
|
48
|
+
Hook invocations that carry CC-parity caller identity (`agent_id` on the
|
|
49
|
+
PreToolUse payload) bypass both gates. The field is injected by the dsh-cc
|
|
50
|
+
bridge when the caller is a live subagent — it is not user-settable via
|
|
51
|
+
`tool_input` — so workers (critic/executor/marathon, shunt-reader,
|
|
52
|
+
shunt-writer, and any other subagent) paginate and inspect files freely;
|
|
53
|
+
the gate's value lives on the main thread, where the harness read caps
|
|
54
|
+
would make a block pure churn anyway.
|
|
55
|
+
|
|
56
|
+
## Images
|
|
57
|
+
|
|
58
|
+
The Read gate sniffs magic bytes (PNG, JPEG, GIF, WEBP) before the
|
|
59
|
+
thresholds: `read_image` has no offset/limit, so large images are allowed
|
|
60
|
+
regardless of size, extensioned or extensionless. There is no extension
|
|
61
|
+
shortcut — a large **text** file named `*.png` is still gated. Any read
|
|
62
|
+
error falls through to the normal thresholds.
|
|
63
|
+
|
|
46
64
|
## Known limits
|
|
47
65
|
|
|
48
66
|
- Digest line references can go stale after edits — verify with a targeted
|
package/README.zh.md
CHANGED
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
|
|
34
34
|
**shunt-worker 子代理固定 `model: haiku`。** 如果你的部署未配置 haiku 别名,worker 会静默继承父级的模型路由——一切照常工作,但**节省的 token 为零**。要真正省钱,请配置 haiku 别名。
|
|
35
35
|
|
|
36
|
+
## 子代理豁免
|
|
37
|
+
|
|
38
|
+
携带 CC 对齐调用者身份(PreToolUse 载荷上的 `agent_id`)的钩子调用会绕过两个门。该字段由 dsh-cc 桥接层在调用者是存活子代理时注入——不是用户可通过 `tool_input` 设置的——因此 worker(critic/executor/marathon、shunt-reader、shunt-writer 及其他子代理)可以自由分页和查看文件;门的价值在主线程,在那里 harness 读取上限本会让拦截变成纯粹的往返浪费。
|
|
39
|
+
|
|
40
|
+
## 图片
|
|
41
|
+
|
|
42
|
+
Read 门在阈值之前按魔数(PNG、JPEG、GIF、WEBP)嗅探:`read_image` 没有 offset/limit,因此大图片无论多大都放行,带扩展名或不带扩展名均可。没有基于扩展名的捷径——名为 `*.png` 的大**文本**文件仍然会被拦截。任何读取错误都会回退到正常阈值。
|
|
43
|
+
|
|
36
44
|
## 已知限制
|
|
37
45
|
|
|
38
46
|
- 摘要中的行号引用在编辑后可能失效——在引用位置编辑前,请先用带 offset/limit 的定向读取确认。
|
|
@@ -60,6 +60,10 @@ const command = typeof payload.tool_input?.command === 'string' ? payload.tool_i
|
|
|
60
60
|
// Kill switch.
|
|
61
61
|
if (truthy(process.env.SHUNT_DISABLED)) allow()
|
|
62
62
|
|
|
63
|
+
// Subagent exemption: invocations carrying CC-parity caller identity
|
|
64
|
+
// (agent_id, bridge-injected for live subagents) bypass the gate.
|
|
65
|
+
if (typeof payload.agent_id === 'string' && payload.agent_id !== '') allow()
|
|
66
|
+
|
|
63
67
|
// No command, or a targeted (piped / redirected) command.
|
|
64
68
|
if (!command || command.includes('|') || command.includes('>') || !READ_COMMAND_RE.test(command)) allow()
|
|
65
69
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Allow contract: {"decision": "allow"} on stdout, exit 0.
|
|
11
11
|
* Block contract: one-line {"decision": "block", "reason": "..."} on stdout, exit 0.
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
13
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync } from 'node:fs'
|
|
14
14
|
|
|
15
15
|
const DEFAULT_MIN_LINES = 350
|
|
16
16
|
const DEFAULT_MAX_BYTES = 100000
|
|
@@ -61,6 +61,10 @@ const limit = toolInput.limit
|
|
|
61
61
|
// Kill switch.
|
|
62
62
|
if (truthy(process.env.SHUNT_DISABLED)) allow()
|
|
63
63
|
|
|
64
|
+
// Subagent exemption: invocations carrying CC-parity caller identity
|
|
65
|
+
// (agent_id, bridge-injected for live subagents) bypass the gate.
|
|
66
|
+
if (typeof payload.agent_id === 'string' && payload.agent_id !== '') allow()
|
|
67
|
+
|
|
64
68
|
// Targeted reads always pass — the caller already knows what it needs.
|
|
65
69
|
if (offset !== undefined || limit !== undefined) allow()
|
|
66
70
|
|
|
@@ -75,6 +79,31 @@ try {
|
|
|
75
79
|
}
|
|
76
80
|
if (!stat.isFile()) allow()
|
|
77
81
|
|
|
82
|
+
// Magic-byte image sniff: read_image has no offset/limit and accepts
|
|
83
|
+
// extensionless paths by content sniffing, so large images can never be
|
|
84
|
+
// paginated — allow real image content. No extension shortcut: a text file
|
|
85
|
+
// named *.png stays gated. Any read error falls through to the thresholds.
|
|
86
|
+
try {
|
|
87
|
+
const fd = openSync(filePath, 'r')
|
|
88
|
+
try {
|
|
89
|
+
const head = Buffer.alloc(12)
|
|
90
|
+
const n = readSync(fd, head, 0, 12, 0)
|
|
91
|
+
const b = head.subarray(0, n)
|
|
92
|
+
const isPng = n >= 8 && b.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
|
|
93
|
+
const isJpeg = n >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff
|
|
94
|
+
const isGif = n >= 4 && b.subarray(0, 4).equals(Buffer.from('GIF8', 'ascii'))
|
|
95
|
+
const isWebp =
|
|
96
|
+
n >= 12 &&
|
|
97
|
+
b.subarray(0, 4).equals(Buffer.from('RIFF', 'ascii')) &&
|
|
98
|
+
b.subarray(8, 12).equals(Buffer.from('WEBP', 'ascii'))
|
|
99
|
+
if (isPng || isJpeg || isGif || isWebp) allow()
|
|
100
|
+
} finally {
|
|
101
|
+
closeSync(fd)
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// fall through
|
|
105
|
+
}
|
|
106
|
+
|
|
78
107
|
const minLines = intEnv('SHUNT_MIN_LINES', DEFAULT_MIN_LINES)
|
|
79
108
|
const maxBytes = intEnv('SHUNT_MAX_BYTES', DEFAULT_MAX_BYTES)
|
|
80
109
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-cc/plugin-dsh-cc-shunt",
|
|
3
|
-
"version": "0.8.1
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Official dsh-cc plugin: hard PreToolUse gates redirect bulk reads and boilerplate generation to cheap-lane shunt-worker subagents to save main-context tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|