@namzu/sdk 63.1.0 → 64.0.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -1
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/compaction/hooks.d.ts +57 -0
  6. package/dist/compaction/hooks.d.ts.map +1 -0
  7. package/dist/compaction/hooks.js +44 -0
  8. package/dist/compaction/hooks.js.map +1 -0
  9. package/dist/compaction/manual.d.ts +9 -0
  10. package/dist/compaction/manual.d.ts.map +1 -1
  11. package/dist/compaction/manual.js +74 -5
  12. package/dist/compaction/manual.js.map +1 -1
  13. package/dist/compaction/model-summary.d.ts +6 -0
  14. package/dist/compaction/model-summary.d.ts.map +1 -1
  15. package/dist/compaction/model-summary.js +13 -3
  16. package/dist/compaction/model-summary.js.map +1 -1
  17. package/dist/compaction/verifier.d.ts +8 -2
  18. package/dist/compaction/verifier.d.ts.map +1 -1
  19. package/dist/compaction/verifier.js +23 -7
  20. package/dist/compaction/verifier.js.map +1 -1
  21. package/dist/plugin/hook-context.d.ts +16 -0
  22. package/dist/plugin/hook-context.d.ts.map +1 -1
  23. package/dist/plugin/hook-context.js +40 -0
  24. package/dist/plugin/hook-context.js.map +1 -1
  25. package/dist/plugin/index.d.ts +4 -3
  26. package/dist/plugin/index.d.ts.map +1 -1
  27. package/dist/plugin/index.js +3 -2
  28. package/dist/plugin/index.js.map +1 -1
  29. package/dist/plugin/shell-hook-command.d.ts +25 -0
  30. package/dist/plugin/shell-hook-command.d.ts.map +1 -0
  31. package/dist/plugin/shell-hook-command.js +218 -0
  32. package/dist/plugin/shell-hook-command.js.map +1 -0
  33. package/dist/plugin/shell-hook-tool-names.d.ts +20 -0
  34. package/dist/plugin/shell-hook-tool-names.d.ts.map +1 -0
  35. package/dist/plugin/shell-hook-tool-names.js +43 -0
  36. package/dist/plugin/shell-hook-tool-names.js.map +1 -0
  37. package/dist/plugin/shell-hook.d.ts +62 -20
  38. package/dist/plugin/shell-hook.d.ts.map +1 -1
  39. package/dist/plugin/shell-hook.js +372 -85
  40. package/dist/plugin/shell-hook.js.map +1 -1
  41. package/dist/public-runtime.d.ts +3 -1
  42. package/dist/public-runtime.d.ts.map +1 -1
  43. package/dist/public-runtime.js +2 -1
  44. package/dist/public-runtime.js.map +1 -1
  45. package/dist/public-types.d.ts +2 -1
  46. package/dist/public-types.d.ts.map +1 -1
  47. package/dist/runtime/query/compact-session.d.ts +7 -1
  48. package/dist/runtime/query/compact-session.d.ts.map +1 -1
  49. package/dist/runtime/query/compact-session.js +8 -3
  50. package/dist/runtime/query/compact-session.js.map +1 -1
  51. package/dist/runtime/query/executor/tool-call-admission.d.ts +1 -1
  52. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  53. package/dist/runtime/query/executor/tool-call-admission.js +4 -3
  54. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  55. package/dist/runtime/query/executor.d.ts.map +1 -1
  56. package/dist/runtime/query/executor.js +5 -3
  57. package/dist/runtime/query/executor.js.map +1 -1
  58. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  59. package/dist/runtime/query/finalize-turn.js +9 -2
  60. package/dist/runtime/query/finalize-turn.js.map +1 -1
  61. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  62. package/dist/runtime/query/iteration/index.js +2 -1
  63. package/dist/runtime/query/iteration/index.js.map +1 -1
  64. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  65. package/dist/runtime/query/iteration/phases/compaction.js +24 -12
  66. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  67. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  68. package/dist/runtime/query/iteration/phases/tool-review.js +2 -0
  69. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  70. package/dist/runtime/query/plugin-hooks.d.ts +13 -0
  71. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  72. package/dist/runtime/query/plugin-hooks.js +29 -2
  73. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  74. package/dist/types/agent/query.d.ts +8 -0
  75. package/dist/types/agent/query.d.ts.map +1 -1
  76. package/dist/types/plugin/index.d.ts +55 -11
  77. package/dist/types/plugin/index.d.ts.map +1 -1
  78. package/dist/types/plugin/index.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/agents/QueryAgent.ts +2 -1
  81. package/src/compaction/hooks.ts +104 -0
  82. package/src/compaction/manual.ts +117 -4
  83. package/src/compaction/model-summary.ts +19 -3
  84. package/src/compaction/verifier.ts +33 -3
  85. package/src/plugin/hook-context.ts +46 -0
  86. package/src/plugin/index.ts +14 -0
  87. package/src/plugin/shell-hook-command.ts +215 -0
  88. package/src/plugin/shell-hook-tool-names.ts +46 -0
  89. package/src/plugin/shell-hook.ts +405 -79
  90. package/src/public-runtime.ts +12 -0
  91. package/src/public-types.ts +2 -0
  92. package/src/runtime/query/compact-session.ts +16 -4
  93. package/src/runtime/query/executor/tool-call-admission.ts +10 -2
  94. package/src/runtime/query/executor.ts +5 -2
  95. package/src/runtime/query/finalize-turn.ts +9 -2
  96. package/src/runtime/query/iteration/index.ts +2 -1
  97. package/src/runtime/query/iteration/phases/compaction.ts +31 -12
  98. package/src/runtime/query/iteration/phases/tool-review.ts +2 -0
  99. package/src/runtime/query/plugin-hooks.ts +32 -2
  100. package/src/types/agent/query.ts +9 -0
  101. package/src/types/plugin/index.ts +66 -22
@@ -0,0 +1,215 @@
1
+ /**
2
+ * What a hook's `if` on a shell command sees beyond the line as typed.
3
+ *
4
+ * A filter such as `Bash(git push *)` is written about a command, and the
5
+ * same command runs behind a wrapper (`sudo git push`, `env A=1 git push`,
6
+ * `nohup git push &`, `xargs git push`, `find . -exec git push {} ;`), under
7
+ * its full path (`/usr/bin/git push`) and with options in front of its
8
+ * subcommand (`git -C repo push`). The hook is attached to stop things, so a
9
+ * filter that misses one of those spellings switches the guard off. Each
10
+ * command of the line is therefore also read with its wrappers peeled, its
11
+ * program named by its basename and git's global options dropped, and the
12
+ * filter matches if any of those readings does.
13
+ *
14
+ * A command whose name is not known until the line runs (`$(echo git) push`,
15
+ * `$CMD push`) cannot be compared with anything: `unknowable` says so, and
16
+ * the caller runs the hook.
17
+ */
18
+
19
+ import { basename, lexShellCommandLine } from '../authorization/shell-lexer.js'
20
+
21
+ export interface HookCommandReading {
22
+ /** Every spelling of every command the line runs, as words joined by one space. */
23
+ readonly candidates: readonly string[]
24
+ /** The line runs something whose name cannot be read from it. */
25
+ readonly unknowable: boolean
26
+ }
27
+
28
+ const MAX_VARIANTS = 48
29
+
30
+ /** Options of a wrapper that take the next word as their value. */
31
+ const WRAPPER_VALUE_OPTIONS: Readonly<Record<string, ReadonlySet<string>>> = {
32
+ env: new Set(['-u', '--unset', '-C', '--chdir', '-S', '--split-string']),
33
+ sudo: new Set([
34
+ '-u',
35
+ '--user',
36
+ '-g',
37
+ '--group',
38
+ '-h',
39
+ '--host',
40
+ '-p',
41
+ '--prompt',
42
+ '-C',
43
+ '-D',
44
+ '-R',
45
+ '-T',
46
+ '-U',
47
+ ]),
48
+ doas: new Set(['-u', '-C']),
49
+ nice: new Set(['-n', '--adjustment']),
50
+ ionice: new Set(['-c', '-n', '-p', '-P', '-u']),
51
+ timeout: new Set(['-s', '--signal', '-k', '--kill-after']),
52
+ xargs: new Set([
53
+ '-I',
54
+ '-i',
55
+ '-n',
56
+ '-P',
57
+ '-d',
58
+ '-L',
59
+ '-l',
60
+ '-s',
61
+ '-E',
62
+ '-e',
63
+ '-a',
64
+ '--max-args',
65
+ '--max-procs',
66
+ '--delimiter',
67
+ '--max-lines',
68
+ '--max-chars',
69
+ '--arg-file',
70
+ ]),
71
+ stdbuf: new Set(['-i', '-o', '-e']),
72
+ command: new Set(),
73
+ nohup: new Set(),
74
+ exec: new Set(['-a']),
75
+ builtin: new Set(),
76
+ time: new Set(['-f', '--format', '-o', '--output']),
77
+ setsid: new Set(),
78
+ chroot: new Set(),
79
+ busybox: new Set(),
80
+ watch: new Set(['-n', '--interval']),
81
+ flock: new Set(['-w', '--timeout', '-E', '--conflict-exit-code']),
82
+ }
83
+
84
+ /** Global options of git that stand between `git` and its subcommand. */
85
+ const GIT_VALUE_OPTIONS = new Set([
86
+ '-C',
87
+ '-c',
88
+ '--git-dir',
89
+ '--work-tree',
90
+ '--namespace',
91
+ '--exec-path',
92
+ '--super-prefix',
93
+ '--config-env',
94
+ ])
95
+
96
+ /** The words after the wrapper at `words[0]`, or `undefined` when it is not one. */
97
+ function peelWrapper(words: readonly string[]): readonly string[] | undefined {
98
+ const head = basename(words[0] ?? '')
99
+ const valueOptions = WRAPPER_VALUE_OPTIONS[head]
100
+ if (valueOptions === undefined) return undefined
101
+ let index = 1
102
+ while (index < words.length) {
103
+ const word = words[index] as string
104
+ if (word === '--') {
105
+ index += 1
106
+ break
107
+ }
108
+ if (head === 'env' && /^[A-Za-z_][A-Za-z0-9_]*=/.test(word)) {
109
+ index += 1
110
+ continue
111
+ }
112
+ if (word.startsWith('-') && word.length > 1) {
113
+ index += valueOptions.has(word) ? 2 : 1
114
+ continue
115
+ }
116
+ // `timeout 5 cmd`, `nice 10 cmd` (old form): a bare duration or number.
117
+ if ((head === 'timeout' || head === 'nice') && /^\d+(?:\.\d+)?[smhd]?$/.test(word)) {
118
+ index += 1
119
+ continue
120
+ }
121
+ break
122
+ }
123
+ // `flock <lockfile> cmd`: the first word after the options is the lock, not the command.
124
+ if (head === 'flock') index += 1
125
+ const rest = words.slice(index)
126
+ return rest.length > 0 ? rest : undefined
127
+ }
128
+
129
+ /** The commands after each `-exec`, `-execdir` or `-ok` of a `find`. */
130
+ function findExecutions(words: readonly string[]): string[][] {
131
+ if (basename(words[0] ?? '') !== 'find') return []
132
+ const found: string[][] = []
133
+ for (let index = 1; index < words.length; index += 1) {
134
+ const word = words[index]
135
+ if (word !== '-exec' && word !== '-execdir' && word !== '-ok' && word !== '-okdir') continue
136
+ const body: string[] = []
137
+ for (let next = index + 1; next < words.length; next += 1) {
138
+ const each = words[next] as string
139
+ if (each === ';' || each === ';' || each === '+') break
140
+ body.push(each)
141
+ }
142
+ if (body.length > 0) found.push(body)
143
+ }
144
+ return found
145
+ }
146
+
147
+ /** `git -C repo push` as `git push`. */
148
+ function dropGitGlobals(words: readonly string[]): readonly string[] | undefined {
149
+ if (basename(words[0] ?? '') !== 'git') return undefined
150
+ const kept: string[] = ['git']
151
+ let index = 1
152
+ let dropped = false
153
+ while (index < words.length) {
154
+ const word = words[index] as string
155
+ if (!word.startsWith('-')) break
156
+ const name = word.split('=')[0] as string
157
+ if (GIT_VALUE_OPTIONS.has(name)) {
158
+ index += word.includes('=') ? 1 : 2
159
+ dropped = true
160
+ } else if (
161
+ word === '--no-pager' ||
162
+ word === '-p' ||
163
+ word === '-P' ||
164
+ word === '--paginate' ||
165
+ word === '--no-optional-locks' ||
166
+ word === '--bare' ||
167
+ word === '--no-replace-objects'
168
+ ) {
169
+ index += 1
170
+ dropped = true
171
+ } else {
172
+ break
173
+ }
174
+ }
175
+ if (!dropped) return undefined
176
+ return [...kept, ...words.slice(index)]
177
+ }
178
+
179
+ function variantsOf(start: readonly string[]): string[][] {
180
+ const seen = new Map<string, string[]>()
181
+ const queue: string[][] = [[...start]]
182
+ while (queue.length > 0 && seen.size < MAX_VARIANTS) {
183
+ const words = queue.shift() as string[]
184
+ const key = words.join('\u0000')
185
+ if (seen.has(key)) continue
186
+ seen.set(key, words)
187
+ const first = words[0]
188
+ if (first === undefined) continue
189
+ const peeled = peelWrapper(words)
190
+ if (peeled !== undefined) queue.push([...peeled])
191
+ for (const body of findExecutions(words)) queue.push(body)
192
+ const git = dropGitGlobals(words)
193
+ if (git !== undefined) queue.push([...git])
194
+ const base = basename(first)
195
+ if (base !== first) queue.push([base, ...words.slice(1)])
196
+ }
197
+ return [...seen.values()]
198
+ }
199
+
200
+ export function readHookCommand(line: string): HookCommandReading {
201
+ const lexed = lexShellCommandLine(line, { dialect: 'sh' })
202
+ if (!lexed.complete) return { candidates: [], unknowable: true }
203
+ const candidates = new Set<string>()
204
+ let unknowable = false
205
+ for (const command of lexed.commands) {
206
+ const words = command.words.slice(command.assignments)
207
+ const head = words[0]
208
+ if (head === undefined) continue
209
+ if (head.expands && /[$`]/.test(head.text)) unknowable = true
210
+ for (const variant of variantsOf(words.map((word) => word.value))) {
211
+ candidates.add(variant.join(' '))
212
+ }
213
+ }
214
+ return { candidates: [...candidates], unknowable }
215
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The names a shell hook may be sent for a tool.
3
+ *
4
+ * A hook sees the tool's own name (`bash`, `web_fetch`) unless its entry asks
5
+ * for the written one (`toolNames: 'written'`), the capitalised name a person
6
+ * writes in a matcher for that tool (`Bash`, `WebFetch`). An entry read from
7
+ * the common hook contract asks for it by default.
8
+ *
9
+ * ONE table, from the name a tool is registered under to its written name.
10
+ * A tool with no common name is not in it and keeps its own; a tool-server
11
+ * tool (`mcp__<server>__<tool>`) is never in it. The table is documented in
12
+ * `docs/sdk/hooks.md`, and a test holds the two equal.
13
+ */
14
+
15
+ /** Which spelling of a tool's name an entry receives. */
16
+ export type ShellHookToolNames = 'namzu' | 'written'
17
+
18
+ /** Our tool name to the name written for it. Every key is lower case. */
19
+ export const SHELL_HOOK_WRITTEN_TOOL_NAMES: Readonly<Record<string, string>> = Object.freeze({
20
+ Agent: 'Agent',
21
+ ask_user_question: 'AskUserQuestion',
22
+ bash: 'Bash',
23
+ edit: 'Edit',
24
+ glob: 'Glob',
25
+ grep: 'Grep',
26
+ ls: 'LS',
27
+ lsp: 'LSP',
28
+ monitor: 'Monitor',
29
+ read: 'Read',
30
+ search_tools: 'ToolSearch',
31
+ skill: 'Skill',
32
+ task_create: 'TaskCreate',
33
+ task_list: 'TaskList',
34
+ task_update: 'TaskUpdate',
35
+ web_fetch: 'WebFetch',
36
+ web_search: 'WebSearch',
37
+ write: 'Write',
38
+ })
39
+
40
+ /** The name a hook is sent for `toolName` under the entry's `toolNames`. */
41
+ export function shellHookToolName(toolName: string, mode: ShellHookToolNames | undefined): string {
42
+ if (mode !== 'written') return toolName
43
+ return Object.hasOwn(SHELL_HOOK_WRITTEN_TOOL_NAMES, toolName)
44
+ ? (SHELL_HOOK_WRITTEN_TOOL_NAMES[toolName] as string)
45
+ : toolName
46
+ }