@namzu/sdk 45.0.0 → 46.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 (281) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/dist/authorization/gate.d.ts +5 -2
  3. package/dist/authorization/gate.d.ts.map +1 -1
  4. package/dist/authorization/gate.js +35 -4
  5. package/dist/authorization/gate.js.map +1 -1
  6. package/dist/authorization/rules.d.ts.map +1 -1
  7. package/dist/authorization/rules.js +56 -6
  8. package/dist/authorization/rules.js.map +1 -1
  9. package/dist/authorization/shell-lexer.d.ts +38 -0
  10. package/dist/authorization/shell-lexer.d.ts.map +1 -1
  11. package/dist/authorization/shell-lexer.js +88 -57
  12. package/dist/authorization/shell-lexer.js.map +1 -1
  13. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  14. package/dist/bridge/a2a/mapper.js +2 -0
  15. package/dist/bridge/a2a/mapper.js.map +1 -1
  16. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  17. package/dist/bridge/sse/mapper.js +1 -0
  18. package/dist/bridge/sse/mapper.js.map +1 -1
  19. package/dist/directory/types.d.ts +2 -0
  20. package/dist/directory/types.d.ts.map +1 -1
  21. package/dist/directory/types.js.map +1 -1
  22. package/dist/manager/resident/outbox.d.ts +4 -4
  23. package/dist/pricing/catalogue.generated.d.ts.map +1 -1
  24. package/dist/pricing/catalogue.generated.js +28 -4
  25. package/dist/pricing/catalogue.generated.js.map +1 -1
  26. package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
  27. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  28. package/dist/prompt/coding-agent-doctrine.js +1 -0
  29. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  30. package/dist/public-runtime.d.ts +6 -3
  31. package/dist/public-runtime.d.ts.map +1 -1
  32. package/dist/public-runtime.js +14 -2
  33. package/dist/public-runtime.js.map +1 -1
  34. package/dist/public-tools.d.ts +7 -2
  35. package/dist/public-tools.d.ts.map +1 -1
  36. package/dist/public-tools.js +15 -2
  37. package/dist/public-tools.js.map +1 -1
  38. package/dist/public-types.d.ts +8 -2
  39. package/dist/public-types.d.ts.map +1 -1
  40. package/dist/registry/tool/callable.d.ts +22 -0
  41. package/dist/registry/tool/callable.d.ts.map +1 -0
  42. package/dist/registry/tool/callable.js +29 -0
  43. package/dist/registry/tool/callable.js.map +1 -0
  44. package/dist/registry/tool/execute.d.ts.map +1 -1
  45. package/dist/registry/tool/execute.js +8 -1
  46. package/dist/registry/tool/execute.js.map +1 -1
  47. package/dist/runtime/query/declined.d.ts +12 -0
  48. package/dist/runtime/query/declined.d.ts.map +1 -0
  49. package/dist/runtime/query/declined.js +12 -0
  50. package/dist/runtime/query/declined.js.map +1 -0
  51. package/dist/runtime/query/executor/tool-call-admission.d.ts +37 -11
  52. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  53. package/dist/runtime/query/executor/tool-call-admission.js +38 -12
  54. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  55. package/dist/runtime/query/executor.d.ts +7 -3
  56. package/dist/runtime/query/executor.d.ts.map +1 -1
  57. package/dist/runtime/query/executor.js +32 -7
  58. package/dist/runtime/query/executor.js.map +1 -1
  59. package/dist/runtime/query/index.d.ts.map +1 -1
  60. package/dist/runtime/query/index.js +1 -0
  61. package/dist/runtime/query/index.js.map +1 -1
  62. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  63. package/dist/runtime/query/iteration/index.js +7 -0
  64. package/dist/runtime/query/iteration/index.js.map +1 -1
  65. package/dist/runtime/query/iteration/phases/context.d.ts +6 -0
  66. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  67. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  68. package/dist/runtime/query/iteration/phases/handoff.d.ts +22 -0
  69. package/dist/runtime/query/iteration/phases/handoff.d.ts.map +1 -0
  70. package/dist/runtime/query/iteration/phases/handoff.js +65 -0
  71. package/dist/runtime/query/iteration/phases/handoff.js.map +1 -0
  72. package/dist/runtime/query/iteration/phases/index.d.ts +1 -0
  73. package/dist/runtime/query/iteration/phases/index.d.ts.map +1 -1
  74. package/dist/runtime/query/iteration/phases/index.js +1 -0
  75. package/dist/runtime/query/iteration/phases/index.js.map +1 -1
  76. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  77. package/dist/runtime/query/iteration/phases/tool-review.js +8 -3
  78. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  79. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  80. package/dist/runtime/query/resume-pending.js +3 -2
  81. package/dist/runtime/query/resume-pending.js.map +1 -1
  82. package/dist/runtime/query/review-policy.d.ts +43 -0
  83. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  84. package/dist/runtime/query/review-policy.js +63 -19
  85. package/dist/runtime/query/review-policy.js.map +1 -1
  86. package/dist/schedules/cron.d.ts +21 -0
  87. package/dist/schedules/cron.d.ts.map +1 -0
  88. package/dist/schedules/cron.js +167 -0
  89. package/dist/schedules/cron.js.map +1 -0
  90. package/dist/schedules/describe.d.ts +14 -0
  91. package/dist/schedules/describe.d.ts.map +1 -0
  92. package/dist/schedules/describe.js +133 -0
  93. package/dist/schedules/describe.js.map +1 -0
  94. package/dist/schedules/errors.d.ts +11 -0
  95. package/dist/schedules/errors.d.ts.map +1 -0
  96. package/dist/schedules/errors.js +15 -0
  97. package/dist/schedules/errors.js.map +1 -0
  98. package/dist/schedules/evaluate.d.ts +35 -0
  99. package/dist/schedules/evaluate.d.ts.map +1 -0
  100. package/dist/schedules/evaluate.js +158 -0
  101. package/dist/schedules/evaluate.js.map +1 -0
  102. package/dist/schedules/index.d.ts +11 -0
  103. package/dist/schedules/index.d.ts.map +1 -0
  104. package/dist/schedules/index.js +8 -0
  105. package/dist/schedules/index.js.map +1 -0
  106. package/dist/schedules/next-fire.d.ts +50 -0
  107. package/dist/schedules/next-fire.d.ts.map +1 -0
  108. package/dist/schedules/next-fire.js +250 -0
  109. package/dist/schedules/next-fire.js.map +1 -0
  110. package/dist/schedules/spec.d.ts +30 -0
  111. package/dist/schedules/spec.d.ts.map +1 -0
  112. package/dist/schedules/spec.js +169 -0
  113. package/dist/schedules/spec.js.map +1 -0
  114. package/dist/schedules/types.d.ts +144 -0
  115. package/dist/schedules/types.d.ts.map +1 -0
  116. package/dist/schedules/types.js +11 -0
  117. package/dist/schedules/types.js.map +1 -0
  118. package/dist/schedules/tz.d.ts +44 -0
  119. package/dist/schedules/tz.d.ts.map +1 -0
  120. package/dist/schedules/tz.js +141 -0
  121. package/dist/schedules/tz.js.map +1 -0
  122. package/dist/skills/index.d.ts +1 -1
  123. package/dist/skills/index.d.ts.map +1 -1
  124. package/dist/skills/index.js +1 -1
  125. package/dist/skills/index.js.map +1 -1
  126. package/dist/skills/loader.d.ts +16 -3
  127. package/dist/skills/loader.d.ts.map +1 -1
  128. package/dist/skills/loader.js +48 -4
  129. package/dist/skills/loader.js.map +1 -1
  130. package/dist/skills/registry.d.ts +6 -0
  131. package/dist/skills/registry.d.ts.map +1 -1
  132. package/dist/skills/registry.js +1 -0
  133. package/dist/skills/registry.js.map +1 -1
  134. package/dist/tools/builtins/browser-url.d.ts +83 -0
  135. package/dist/tools/builtins/browser-url.d.ts.map +1 -0
  136. package/dist/tools/builtins/browser-url.js +240 -0
  137. package/dist/tools/builtins/browser-url.js.map +1 -0
  138. package/dist/tools/builtins/browser.d.ts +367 -0
  139. package/dist/tools/builtins/browser.d.ts.map +1 -0
  140. package/dist/tools/builtins/browser.js +704 -0
  141. package/dist/tools/builtins/browser.js.map +1 -0
  142. package/dist/tools/builtins/computer-use-coordinates.d.ts +65 -0
  143. package/dist/tools/builtins/computer-use-coordinates.d.ts.map +1 -0
  144. package/dist/tools/builtins/computer-use-coordinates.js +123 -0
  145. package/dist/tools/builtins/computer-use-coordinates.js.map +1 -0
  146. package/dist/tools/builtins/computer-use-image.d.ts +77 -0
  147. package/dist/tools/builtins/computer-use-image.d.ts.map +1 -0
  148. package/dist/tools/builtins/computer-use-image.js +223 -0
  149. package/dist/tools/builtins/computer-use-image.js.map +1 -0
  150. package/dist/tools/builtins/computer-use.d.ts +519 -14
  151. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  152. package/dist/tools/builtins/computer-use.js +1188 -183
  153. package/dist/tools/builtins/computer-use.js.map +1 -1
  154. package/dist/tools/builtins/index.d.ts +2 -1
  155. package/dist/tools/builtins/index.d.ts.map +1 -1
  156. package/dist/tools/builtins/index.js +1 -1
  157. package/dist/tools/builtins/index.js.map +1 -1
  158. package/dist/tools/builtins/skill.d.ts +74 -0
  159. package/dist/tools/builtins/skill.d.ts.map +1 -1
  160. package/dist/tools/builtins/skill.js +214 -159
  161. package/dist/tools/builtins/skill.js.map +1 -1
  162. package/dist/tools/defineTool.d.ts +4 -0
  163. package/dist/tools/defineTool.d.ts.map +1 -1
  164. package/dist/tools/defineTool.js +8 -0
  165. package/dist/tools/defineTool.js.map +1 -1
  166. package/dist/tools/schedules/index.d.ts +5 -0
  167. package/dist/tools/schedules/index.d.ts.map +1 -0
  168. package/dist/tools/schedules/index.js +4 -0
  169. package/dist/tools/schedules/index.js.map +1 -0
  170. package/dist/tools/schedules/loop-tool.d.ts +14 -0
  171. package/dist/tools/schedules/loop-tool.d.ts.map +1 -0
  172. package/dist/tools/schedules/loop-tool.js +81 -0
  173. package/dist/tools/schedules/loop-tool.js.map +1 -0
  174. package/dist/tools/schedules/present.d.ts +16 -0
  175. package/dist/tools/schedules/present.d.ts.map +1 -0
  176. package/dist/tools/schedules/present.js +71 -0
  177. package/dist/tools/schedules/present.js.map +1 -0
  178. package/dist/tools/schedules/prompt-scan.d.ts +17 -0
  179. package/dist/tools/schedules/prompt-scan.d.ts.map +1 -0
  180. package/dist/tools/schedules/prompt-scan.js +92 -0
  181. package/dist/tools/schedules/prompt-scan.js.map +1 -0
  182. package/dist/tools/schedules/schedule-tool.d.ts +17 -0
  183. package/dist/tools/schedules/schedule-tool.d.ts.map +1 -0
  184. package/dist/tools/schedules/schedule-tool.js +418 -0
  185. package/dist/tools/schedules/schedule-tool.js.map +1 -0
  186. package/dist/tools/schedules/types.d.ts +252 -0
  187. package/dist/tools/schedules/types.d.ts.map +1 -0
  188. package/dist/tools/schedules/types.js +11 -0
  189. package/dist/tools/schedules/types.js.map +1 -0
  190. package/dist/types/authorization/index.d.ts +107 -9
  191. package/dist/types/authorization/index.d.ts.map +1 -1
  192. package/dist/types/authorization/index.js +16 -1
  193. package/dist/types/authorization/index.js.map +1 -1
  194. package/dist/types/browser/index.d.ts +280 -0
  195. package/dist/types/browser/index.d.ts.map +1 -0
  196. package/dist/types/browser/index.js +12 -0
  197. package/dist/types/browser/index.js.map +1 -0
  198. package/dist/types/computer-use/index.d.ts +176 -0
  199. package/dist/types/computer-use/index.d.ts.map +1 -1
  200. package/dist/types/computer-use/index.js.map +1 -1
  201. package/dist/types/session/events.d.ts +6 -0
  202. package/dist/types/session/events.d.ts.map +1 -1
  203. package/dist/types/session/events.js.map +1 -1
  204. package/dist/types/session/records.d.ts +23 -0
  205. package/dist/types/session/records.d.ts.map +1 -1
  206. package/dist/types/session/records.js +9 -0
  207. package/dist/types/session/records.js.map +1 -1
  208. package/dist/types/tool/index.d.ts +60 -0
  209. package/dist/types/tool/index.d.ts.map +1 -1
  210. package/dist/types/tool/index.js.map +1 -1
  211. package/dist/types/tool/presentation.d.ts +7 -0
  212. package/dist/types/tool/presentation.d.ts.map +1 -1
  213. package/dist/utils/frontmatter.d.ts +18 -2
  214. package/dist/utils/frontmatter.d.ts.map +1 -1
  215. package/dist/utils/frontmatter.js +13 -3
  216. package/dist/utils/frontmatter.js.map +1 -1
  217. package/dist/utils/id.d.ts +8 -0
  218. package/dist/utils/id.d.ts.map +1 -1
  219. package/dist/utils/id.js +12 -0
  220. package/dist/utils/id.js.map +1 -1
  221. package/package.json +3 -1
  222. package/src/authorization/gate.ts +37 -4
  223. package/src/authorization/rules.ts +55 -7
  224. package/src/authorization/shell-lexer.ts +109 -51
  225. package/src/bridge/a2a/mapper.ts +2 -0
  226. package/src/bridge/sse/mapper.ts +1 -0
  227. package/src/directory/types.ts +2 -0
  228. package/src/pricing/catalogue.generated.ts +28 -4
  229. package/src/pricing/rates.source.json +27 -6
  230. package/src/prompt/coding-agent-doctrine.ts +1 -0
  231. package/src/public-runtime.ts +43 -1
  232. package/src/public-tools.ts +54 -1
  233. package/src/public-types.ts +55 -0
  234. package/src/registry/tool/callable.ts +37 -0
  235. package/src/registry/tool/execute.ts +8 -1
  236. package/src/runtime/query/declined.ts +12 -0
  237. package/src/runtime/query/executor/tool-call-admission.ts +60 -16
  238. package/src/runtime/query/executor.ts +36 -5
  239. package/src/runtime/query/index.ts +1 -0
  240. package/src/runtime/query/iteration/index.ts +8 -0
  241. package/src/runtime/query/iteration/phases/context.ts +6 -0
  242. package/src/runtime/query/iteration/phases/handoff.ts +74 -0
  243. package/src/runtime/query/iteration/phases/index.ts +1 -0
  244. package/src/runtime/query/iteration/phases/tool-review.ts +11 -4
  245. package/src/runtime/query/resume-pending.ts +3 -2
  246. package/src/runtime/query/review-policy.ts +107 -18
  247. package/src/schedules/cron.ts +202 -0
  248. package/src/schedules/describe.ts +138 -0
  249. package/src/schedules/errors.ts +15 -0
  250. package/src/schedules/evaluate.ts +178 -0
  251. package/src/schedules/index.ts +18 -0
  252. package/src/schedules/next-fire.ts +257 -0
  253. package/src/schedules/spec.ts +210 -0
  254. package/src/schedules/types.ts +163 -0
  255. package/src/schedules/tz.ts +155 -0
  256. package/src/skills/index.ts +1 -1
  257. package/src/skills/loader.ts +55 -4
  258. package/src/skills/registry.ts +8 -0
  259. package/src/tools/builtins/browser-url.ts +251 -0
  260. package/src/tools/builtins/browser.ts +817 -0
  261. package/src/tools/builtins/computer-use-coordinates.ts +144 -0
  262. package/src/tools/builtins/computer-use-image.ts +278 -0
  263. package/src/tools/builtins/computer-use.ts +1485 -191
  264. package/src/tools/builtins/index.ts +7 -1
  265. package/src/tools/builtins/skill.ts +304 -156
  266. package/src/tools/defineTool.ts +13 -0
  267. package/src/tools/schedules/index.ts +4 -0
  268. package/src/tools/schedules/loop-tool.ts +85 -0
  269. package/src/tools/schedules/present.ts +88 -0
  270. package/src/tools/schedules/prompt-scan.ts +96 -0
  271. package/src/tools/schedules/schedule-tool.ts +482 -0
  272. package/src/tools/schedules/types.ts +265 -0
  273. package/src/types/authorization/index.ts +81 -2
  274. package/src/types/browser/index.ts +341 -0
  275. package/src/types/computer-use/index.ts +202 -0
  276. package/src/types/session/events.ts +6 -0
  277. package/src/types/session/records.ts +10 -0
  278. package/src/types/tool/index.ts +61 -0
  279. package/src/types/tool/presentation.ts +7 -0
  280. package/src/utils/frontmatter.ts +29 -3
  281. package/src/utils/id.ts +14 -0
@@ -7,7 +7,13 @@ export { LSP_TOOL_NAME, LspTool, getCodeNavigationTools } from './lsp.js'
7
7
  export { GrepTool } from './grep.js'
8
8
  export { JobTool } from './job.js'
9
9
  export { WaitForJobTool } from './wait-for-job.js'
10
- export { SKILL_TOOL_NAME, SkillTool, parseAllowedTools } from './skill.js'
10
+ export { SKILL_TOOL_NAME, SkillTool, createSkillTool, parseAllowedTools } from './skill.js'
11
+ export type {
12
+ SkillDirectoryContext,
13
+ SkillDirectoryRequest,
14
+ SkillDirectoryResolver,
15
+ SkillToolOptions,
16
+ } from './skill.js'
11
17
  export {
12
18
  WEB_FETCH_TOOL_NAME,
13
19
  WEB_SEARCH_TOOL_NAME,
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto'
2
2
  import { z } from 'zod'
3
3
 
4
4
  import { parseAllowedTools } from '../../authorization/skill-grant.js'
5
+ import type { Sandbox } from '../../types/sandbox/index.js'
5
6
  import { isInvocableBy, skillInvocation } from '../../types/skills/index.js'
6
7
  import { defineTool } from '../defineTool.js'
7
8
 
@@ -22,8 +23,63 @@ export { parseAllowedTools }
22
23
  * else, from the next batch — which inverted what skill authors mean by it
23
24
  * and left a model that loaded `allowed-tools: Read Grep` without `bash`.
24
25
  * See `authorization/skill-grant.ts` for what a grant can and cannot do.
26
+ *
27
+ * A skill's body often names files beside it (`scripts/`, `references/`,
28
+ * `assets/`), and the load result carried no directory to open them from. The
29
+ * listing reported the registry's `location`, which is where the HOST reads
30
+ * the skill — a path the model cannot open once its tools run in a sandbox or
31
+ * a remote workspace, so it hard-coded what the skill said to read, or went
32
+ * searching the filesystem. Only the host knows what its tools can reach, so
33
+ * the host says it: {@link SkillToolOptions.resolveModelDirectory}.
25
34
  */
26
35
 
36
+ /** One skill, as the `skill` tool asks a host about it. */
37
+ export interface SkillDirectoryRequest {
38
+ /** The name the registry accepts, namespaced for a plugin skill (`plugin__skill`). */
39
+ readonly name: string
40
+ /**
41
+ * Where the host loads the skill from (`Skill.dirPath`, or the catalog
42
+ * entry's `directory`); undefined when the registry does not say.
43
+ */
44
+ readonly directory: string | undefined
45
+ }
46
+
47
+ /** What the call knows about where the model's tools run. */
48
+ export interface SkillDirectoryContext {
49
+ /** The turn's sandbox, when its tools run in one. Absent on the host. */
50
+ readonly sandbox?: Sandbox
51
+ }
52
+
53
+ /**
54
+ * The directory the model's tools can open for a skill, or undefined when
55
+ * they cannot reach it.
56
+ *
57
+ * Asked per call, with the turn's sandbox, because the answer is a property
58
+ * of where the tools run and not of the skill: the same skill is at its own
59
+ * path on the host, somewhere else inside a container, and absent from a
60
+ * sandbox that does not mount it.
61
+ */
62
+ export type SkillDirectoryResolver = (
63
+ skill: SkillDirectoryRequest,
64
+ context: SkillDirectoryContext,
65
+ ) => string | undefined | Promise<string | undefined>
66
+
67
+ export interface SkillToolOptions {
68
+ /**
69
+ * Say which directory the model can open for each skill.
70
+ *
71
+ * Absent, the tool behaves as it always has: the listing carries the
72
+ * registry's `location` and a load names no directory. Present, a load
73
+ * opens with the directory it returns, or with a line saying the skill's
74
+ * files are not reachable when it returns undefined; the listing carries
75
+ * that `directory` and never the registry's `location`, which is the
76
+ * host's path; and `${CLAUDE_SKILL_DIR}` in `allowed-tools` expands to it,
77
+ * since a command line the model writes names the path the model was
78
+ * given. The skill is still LOADED from the registry's own path.
79
+ */
80
+ readonly resolveModelDirectory?: SkillDirectoryResolver
81
+ }
82
+
27
83
  const inputSchema = z.object({
28
84
  name: z
29
85
  .string()
@@ -53,6 +109,8 @@ interface SkillSnapshot {
53
109
  readonly allowedTools: readonly string[] | undefined
54
110
  /** Bound into the cursor because `${CLAUDE_SKILL_DIR}` in a grant expands to it. */
55
111
  readonly skillDirectory: string | undefined
112
+ /** What the first page opens with: where the skill's files are, or that they are out of reach. */
113
+ readonly header: string
56
114
  readonly invocation: ReturnType<typeof skillInvocation>
57
115
  }
58
116
 
@@ -64,7 +122,10 @@ interface SkillPage {
64
122
  interface ListedSkill {
65
123
  readonly name: string
66
124
  readonly description: string
67
- readonly location: string
125
+ /** The registry's path to the SKILL.md; only when no host resolver is configured. */
126
+ readonly location?: string
127
+ /** The directory the model can open, from the host's resolver. */
128
+ readonly directory?: string
68
129
  readonly allowedTools?: string
69
130
  }
70
131
 
@@ -85,6 +146,7 @@ function snapshotDigest(snapshot: SkillSnapshot): string {
85
146
  ...(snapshot.skillDirectory === undefined
86
147
  ? {}
87
148
  : { skillDirectory: snapshot.skillDirectory }),
149
+ ...(snapshot.header === '' ? {} : { header: snapshot.header }),
88
150
  invocation: snapshot.invocation,
89
151
  }),
90
152
  )
@@ -218,7 +280,9 @@ function pageSkillBody(input: {
218
280
  readonly maxChars: number | undefined
219
281
  }): SkillPage | undefined {
220
282
  const { snapshot, digest, start, notice, maxChars } = input
221
- const remaining = `${snapshot.body.slice(start)}${notice}`
283
+ // The first page only: a continuation is read after it, in the same history.
284
+ const header = start === 0 ? snapshot.header : ''
285
+ const remaining = `${header}${snapshot.body.slice(start)}${notice}`
222
286
  if (maxChars === undefined || maxChars <= 0 || remaining.length <= maxChars) {
223
287
  return { output: remaining }
224
288
  }
@@ -229,7 +293,7 @@ function pageSkillBody(input: {
229
293
  let end = boundaryAtOrBefore(snapshot.body, Math.min(snapshot.body.length - 1, start + maxChars))
230
294
  while (end > start) {
231
295
  const nextCursor = cursorFor(end, digest)
232
- const output = `${snapshot.body.slice(start, end)}${continuationNotice(
296
+ const output = `${header}${snapshot.body.slice(start, end)}${continuationNotice(
233
297
  snapshot.name,
234
298
  nextCursor,
235
299
  )}${notice}`
@@ -242,198 +306,282 @@ function pageSkillBody(input: {
242
306
 
243
307
  export const SKILL_TOOL_NAME = 'skill'
244
308
 
245
- export const SkillTool = defineTool({
246
- name: SKILL_TOOL_NAME,
247
- description:
248
- 'Lists model-invocable skills when called without a name, or loads one skill by its exact listed name. Long lists and bodies return an opaque continuation cursor; keep calling in the same mode with that cursor until no continuation remains. The manifest carries only names and descriptions.',
249
- inputSchema,
250
- category: 'analysis',
251
- permissions: [],
252
- // Reads instructions and changes nothing on disk. What it does change is
253
- // the turn's approvals, through `grantSkillTools`, and only ever towards
254
- // fewer prompts for calls the operator's policy already leaves to review.
255
- readOnly: true,
256
- destructive: false,
257
- concurrencySafe: true,
258
-
259
- async execute(input: SkillInput, context) {
260
- if (!context.skills) {
309
+ /**
310
+ * Build the `skill` tool, with what the host knows about where its model's
311
+ * tools run. {@link SkillTool} is this with no options.
312
+ */
313
+ export function createSkillTool(options: SkillToolOptions = {}) {
314
+ const resolveModelDirectory = options.resolveModelDirectory
315
+ return defineTool({
316
+ name: SKILL_TOOL_NAME,
317
+ description:
318
+ 'Lists model-invocable skills when called without a name, or loads one skill by its exact listed name. Long lists and bodies return an opaque continuation cursor; keep calling in the same mode with that cursor until no continuation remains. The manifest carries only names and descriptions.',
319
+ inputSchema,
320
+ category: 'analysis',
321
+ permissions: [],
322
+ // Reads instructions and changes nothing on disk. What it does change is
323
+ // the turn's approvals, through `grantSkillTools`, and only ever towards
324
+ // fewer prompts for calls the operator's policy already leaves to review.
325
+ readOnly: true,
326
+ destructive: false,
327
+ concurrencySafe: true,
328
+
329
+ // The body is instructions for the model, often a hundred lines; the
330
+ // person needs the row that says which skill was read, not the text.
331
+ presentCall(input: SkillInput) {
332
+ const name = typeof input?.name === 'string' ? input.name : undefined
261
333
  return {
262
- success: false,
263
- output: '',
264
- error:
265
- 'This turn has no skills registry, so there is nothing to load. Proceed without the skill.',
334
+ kind: 'generic',
335
+ presentation: 'activity',
336
+ label:
337
+ name === undefined
338
+ ? input?.cursor === undefined
339
+ ? 'List skills'
340
+ : 'List more skills'
341
+ : `Read skill ${name}${input.cursor === undefined ? '' : ' (continued)'}`,
266
342
  }
267
- }
343
+ },
344
+ presentResult: (_input: SkillInput, result) =>
345
+ result.success ? { kind: 'generic', label: 'read', visibility: 'hidden' } : undefined,
268
346
 
269
- if (input.name === undefined) {
270
- if (!context.skills.catalog) {
347
+ async execute(input: SkillInput, context) {
348
+ if (!context.skills) {
271
349
  return {
272
350
  success: false,
273
351
  output: '',
274
352
  error:
275
- 'This skills registry cannot enumerate model-safe metadata. Use a skill name from the available-skills manifest.',
353
+ 'This turn has no skills registry, so there is nothing to load. Proceed without the skill.',
276
354
  }
277
355
  }
278
- const skills = (await context.skills.catalog())
279
- .filter(
280
- (entry) =>
281
- entry.invocation === undefined ||
282
- entry.invocation === 'model' ||
283
- entry.invocation === 'both',
284
- )
285
- .map(
286
- (entry): ListedSkill => ({
287
- name: entry.registeredName,
288
- description: entry.description,
289
- location: entry.location,
290
- ...(entry.allowedTools === undefined ? {} : { allowedTools: entry.allowedTools }),
291
- }),
356
+
357
+ if (input.name === undefined) {
358
+ if (!context.skills.catalog) {
359
+ return {
360
+ success: false,
361
+ output: '',
362
+ error:
363
+ 'This skills registry cannot enumerate model-safe metadata. Use a skill name from the available-skills manifest.',
364
+ }
365
+ }
366
+ const directoryContext = directoryContextOf(context.sandbox)
367
+ const skills = await Promise.all(
368
+ (await context.skills.catalog())
369
+ .filter(
370
+ (entry) =>
371
+ entry.invocation === undefined ||
372
+ entry.invocation === 'model' ||
373
+ entry.invocation === 'both',
374
+ )
375
+ .map(async (entry): Promise<ListedSkill> => {
376
+ // With a resolver the registry's `location` is left out, not
377
+ // shown beside the directory: it is the host's path, the one
378
+ // a sandboxed model cannot open.
379
+ let where: Pick<ListedSkill, 'location' | 'directory'>
380
+ if (resolveModelDirectory) {
381
+ const directory = nonEmpty(
382
+ await resolveModelDirectory(
383
+ { name: entry.registeredName, directory: entry.directory },
384
+ directoryContext,
385
+ ),
386
+ )
387
+ where = directory === undefined ? {} : { directory }
388
+ } else {
389
+ where = { location: entry.location }
390
+ }
391
+ return {
392
+ name: entry.registeredName,
393
+ description: entry.description,
394
+ ...where,
395
+ ...(entry.allowedTools === undefined ? {} : { allowedTools: entry.allowedTools }),
396
+ }
397
+ }),
292
398
  )
293
- const maxChars = activeOutputCap(context.maxToolOutputChars)
294
- const digest = listDigest(skills, maxChars)
399
+ const maxChars = activeOutputCap(context.maxToolOutputChars)
400
+ const digest = listDigest(skills, maxChars)
401
+ let start = 0
402
+ let warningAlreadyShown = false
403
+ if (input.cursor !== undefined) {
404
+ const parsed = parseListCursor(input.cursor)
405
+ if (!parsed || parsed.digest !== digest || parsed.offset >= skills.length) {
406
+ return {
407
+ success: false,
408
+ output: '',
409
+ error:
410
+ 'The skill-list continuation cursor is stale or invalid. Call skill again without a cursor to read the current catalog.',
411
+ }
412
+ }
413
+ start = parsed.offset
414
+ warningAlreadyShown = parsed.warned
415
+ }
416
+
417
+ const page = pageSkillCatalog({
418
+ skills,
419
+ digest,
420
+ start,
421
+ warningAlreadyShown,
422
+ maxChars,
423
+ })
424
+ if (!page) {
425
+ return {
426
+ success: false,
427
+ output: '',
428
+ error:
429
+ 'The model-visible tool-output budget is too small to list skill metadata safely. Increase maxToolOutputChars and retry.',
430
+ }
431
+ }
432
+
433
+ return {
434
+ success: true,
435
+ output: serializeListPage(page),
436
+ data: {
437
+ kind: 'list',
438
+ count: page.skills.length,
439
+ ...(page.nextCursor === null ? {} : { nextCursor: page.nextCursor }),
440
+ },
441
+ }
442
+ }
443
+
444
+ // The registry answers with a load RESULT, not a skill — the shape
445
+ // mirrors the implementation rather than an adapter, so there is
446
+ // nothing between them to drift.
447
+ const loaded = await context.skills.load(input.name)
448
+ if (!loaded) {
449
+ // Named, with what IS available. A bare "not found" sends the model
450
+ // guessing at spellings, and the manifest it is guessing from is
451
+ // right there in its own prompt.
452
+ const available = context.skills.names()
453
+ return {
454
+ success: false,
455
+ output: '',
456
+ error: `No skill named "${input.name}". Available: ${available.length > 0 ? available.join(', ') : '(none)'}`,
457
+ }
458
+ }
459
+
460
+ const skill = loaded.skill
461
+ const invocation = skillInvocation(skill)
462
+ if (!isInvocableBy(skill, 'model')) {
463
+ // Reachable even though the manifest omits it: the model can name
464
+ // anything, and a check that only filtered the listing would be a
465
+ // menu restriction rather than a kitchen one — the exact defect
466
+ // `allowedTools` had before it was enforced at dispatch.
467
+ return {
468
+ success: false,
469
+ output: '',
470
+ error: `The skill "${input.name}" is ${skillInvocation(skill)}-invocable; it is not for you to run.`,
471
+ }
472
+ }
473
+
474
+ const allowed = parseAllowedTools(skill.metadata.allowedTools)
475
+ // Asked on every call, first page or continuation: the digest binds
476
+ // the answer, so a mount that changed between pages is a stale cursor
477
+ // rather than a second half written for a different directory.
478
+ const modelDirectory = resolveModelDirectory
479
+ ? nonEmpty(
480
+ await resolveModelDirectory(
481
+ { name: input.name, directory: skill.dirPath },
482
+ directoryContextOf(context.sandbox),
483
+ ),
484
+ )
485
+ : undefined
486
+ const snapshot: SkillSnapshot = {
487
+ name: input.name,
488
+ body: skill.body ?? '(this skill has no body)',
489
+ allowedTools: allowed,
490
+ // The model's path when the host gave one: `${CLAUDE_SKILL_DIR}` in a
491
+ // pattern is matched against a command line the model writes, and it
492
+ // writes the path it was told.
493
+ skillDirectory: resolveModelDirectory ? modelDirectory : skill.dirPath,
494
+ header: resolveModelDirectory ? directoryHeader(modelDirectory) : '',
495
+ invocation,
496
+ }
497
+ const digest = snapshotDigest(snapshot)
295
498
  let start = 0
296
- let warningAlreadyShown = false
297
499
  if (input.cursor !== undefined) {
298
- const parsed = parseListCursor(input.cursor)
299
- if (!parsed || parsed.digest !== digest || parsed.offset >= skills.length) {
500
+ const parsed = parseCursor(input.cursor)
501
+ if (
502
+ !parsed ||
503
+ parsed.digest !== digest ||
504
+ parsed.offset >= snapshot.body.length ||
505
+ !isCodePointBoundary(snapshot.body, parsed.offset)
506
+ ) {
300
507
  return {
301
508
  success: false,
302
509
  output: '',
303
- error:
304
- 'The skill-list continuation cursor is stale or invalid. Call skill again without a cursor to read the current catalog.',
510
+ error: `The continuation cursor for "${input.name}" is stale or invalid. Call skill again without a cursor to read the current instructions.`,
305
511
  }
306
512
  }
307
513
  start = parsed.offset
308
- warningAlreadyShown = parsed.warned
309
514
  }
310
515
 
311
- const page = pageSkillCatalog({
312
- skills,
516
+ // Compiled before paging so the notice can say what the grant is, and
517
+ // committed only after paging succeeded: a load that fails here gave
518
+ // the model no instructions, so it must not have approved anything.
519
+ // Idempotent: a continuation call grants the same entries again, and
520
+ // the turn's set keeps one copy.
521
+ let grant: ReturnType<NonNullable<typeof context.grantSkillTools>> | undefined
522
+ if (allowed !== undefined && allowed.length > 0 && context.grantSkillTools) {
523
+ grant = context.grantSkillTools({
524
+ skill: skill.metadata.name,
525
+ allowedTools: allowed,
526
+ ...(snapshot.skillDirectory ? { skillDirectory: snapshot.skillDirectory } : {}),
527
+ })
528
+ }
529
+ const notice = grantNotice(allowed, grant, context.grantSkillTools !== undefined)
530
+ const page = pageSkillBody({
531
+ snapshot,
313
532
  digest,
314
533
  start,
315
- warningAlreadyShown,
316
- maxChars,
534
+ notice,
535
+ maxChars: context.maxToolOutputChars,
317
536
  })
318
537
  if (!page) {
319
538
  return {
320
539
  success: false,
321
540
  output: '',
322
- error:
323
- 'The model-visible tool-output budget is too small to list skill metadata safely. Increase maxToolOutputChars and retry.',
541
+ error: `The model-visible tool-output budget is too small to read "${input.name}" safely. Increase maxToolOutputChars and retry.`,
324
542
  }
325
543
  }
544
+ grant?.commit()
326
545
 
327
546
  return {
328
547
  success: true,
329
- output: serializeListPage(page),
548
+ output: page.output,
330
549
  data: {
331
- kind: 'list',
332
- count: page.skills.length,
333
- ...(page.nextCursor === null ? {} : { nextCursor: page.nextCursor }),
550
+ skill: skill.metadata.name,
551
+ ...(modelDirectory === undefined ? {} : { directory: modelDirectory }),
552
+ ...(allowed === undefined ? {} : { allowedTools: allowed }),
553
+ ...(grant ? { granted: grant.granted, ignored: grant.ignored } : {}),
554
+ ...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
334
555
  },
335
556
  }
336
- }
557
+ },
558
+ })
559
+ }
337
560
 
338
- // The registry answers with a load RESULT, not a skill — the shape
339
- // mirrors the implementation rather than an adapter, so there is
340
- // nothing between them to drift.
341
- const loaded = await context.skills.load(input.name)
342
- if (!loaded) {
343
- // Named, with what IS available. A bare "not found" sends the model
344
- // guessing at spellings, and the manifest it is guessing from is
345
- // right there in its own prompt.
346
- const available = context.skills.names()
347
- return {
348
- success: false,
349
- output: '',
350
- error: `No skill named "${input.name}". Available: ${available.length > 0 ? available.join(', ') : '(none)'}`,
351
- }
352
- }
561
+ /** The `skill` tool with no host options: the listing carries the registry's `location`. */
562
+ export const SkillTool = createSkillTool()
353
563
 
354
- const skill = loaded.skill
355
- const invocation = skillInvocation(skill)
356
- if (!isInvocableBy(skill, 'model')) {
357
- // Reachable even though the manifest omits it: the model can name
358
- // anything, and a check that only filtered the listing would be a
359
- // menu restriction rather than a kitchen one — the exact defect
360
- // `allowedTools` had before it was enforced at dispatch.
361
- return {
362
- success: false,
363
- output: '',
364
- error: `The skill "${input.name}" is ${skillInvocation(skill)}-invocable; it is not for you to run.`,
365
- }
366
- }
564
+ function directoryContextOf(sandbox: Sandbox | undefined): SkillDirectoryContext {
565
+ return sandbox ? { sandbox } : {}
566
+ }
367
567
 
368
- const allowed = parseAllowedTools(skill.metadata.allowedTools)
369
- const snapshot: SkillSnapshot = {
370
- name: input.name,
371
- body: skill.body ?? '(this skill has no body)',
372
- allowedTools: allowed,
373
- skillDirectory: skill.dirPath,
374
- invocation,
375
- }
376
- const digest = snapshotDigest(snapshot)
377
- let start = 0
378
- if (input.cursor !== undefined) {
379
- const parsed = parseCursor(input.cursor)
380
- if (
381
- !parsed ||
382
- parsed.digest !== digest ||
383
- parsed.offset >= snapshot.body.length ||
384
- !isCodePointBoundary(snapshot.body, parsed.offset)
385
- ) {
386
- return {
387
- success: false,
388
- output: '',
389
- error: `The continuation cursor for "${input.name}" is stale or invalid. Call skill again without a cursor to read the current instructions.`,
390
- }
391
- }
392
- start = parsed.offset
393
- }
568
+ /** An empty string names no directory; treated as the resolver saying none. */
569
+ function nonEmpty(directory: string | undefined): string | undefined {
570
+ return directory === undefined || directory === '' ? undefined : directory
571
+ }
394
572
 
395
- // Compiled before paging so the notice can say what the grant is, and
396
- // committed only after paging succeeded: a load that fails here gave
397
- // the model no instructions, so it must not have approved anything.
398
- // Idempotent: a continuation call grants the same entries again, and
399
- // the turn's set keeps one copy.
400
- let grant: ReturnType<NonNullable<typeof context.grantSkillTools>> | undefined
401
- if (allowed !== undefined && allowed.length > 0 && context.grantSkillTools) {
402
- grant = context.grantSkillTools({
403
- skill: skill.metadata.name,
404
- allowedTools: allowed,
405
- ...(snapshot.skillDirectory ? { skillDirectory: snapshot.skillDirectory } : {}),
406
- })
407
- }
408
- const notice = grantNotice(allowed, grant, context.grantSkillTools !== undefined)
409
- const page = pageSkillBody({
410
- snapshot,
411
- digest,
412
- start,
413
- notice,
414
- maxChars: context.maxToolOutputChars,
415
- })
416
- if (!page) {
417
- return {
418
- success: false,
419
- output: '',
420
- error: `The model-visible tool-output budget is too small to read "${input.name}" safely. Increase maxToolOutputChars and retry.`,
421
- }
422
- }
423
- grant?.commit()
424
-
425
- return {
426
- success: true,
427
- output: page.output,
428
- data: {
429
- skill: skill.metadata.name,
430
- ...(allowed === undefined ? {} : { allowedTools: allowed }),
431
- ...(grant ? { granted: grant.granted, ignored: grant.ignored } : {}),
432
- ...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
433
- },
434
- }
435
- },
436
- })
573
+ /**
574
+ * The line a load opens with once a host has said where the skill's files are.
575
+ *
576
+ * The unreachable case is said out loud rather than left blank. A model given
577
+ * no directory for a skill that says "run scripts/render.sh" goes looking for
578
+ * it, and on a sandboxed turn that search can only fail.
579
+ */
580
+ function directoryHeader(directory: string | undefined): string {
581
+ return directory === undefined
582
+ ? "[This skill's directory is not reachable from your tools in this session, so a file these instructions name by a relative path (scripts/, references/, assets/) cannot be opened here. Do not search the filesystem for it; if the task needs one, say so.]\n\n"
583
+ : `[Skill directory: ${directory}. Relative paths in these instructions, such as scripts/, references/ or assets/, are inside it.]\n\n`
584
+ }
437
585
 
438
586
  /**
439
587
  * What the model is told about `allowed-tools`.
@@ -21,6 +21,8 @@ export interface DefineToolOptions<S extends z.ZodType> {
21
21
  readOnly: boolean | ((input: z.infer<S>) => boolean)
22
22
  destructive: boolean | ((input: z.infer<S>) => boolean)
23
23
  concurrencySafe: boolean
24
+ /** Whether this exact call sends the screen to the provider; see {@link ToolDefinition.capturesScreen}. */
25
+ capturesScreen?: boolean | ((input: z.infer<S>) => boolean)
24
26
  /** Batch ordering boundary; see {@link ToolDefinition.executionBarrier}. */
25
27
  executionBarrier?: boolean
26
28
  tier?: string
@@ -65,6 +67,8 @@ export interface DefineToolOptions<S extends z.ZodType> {
65
67
  commandDialect?: ToolDefinition['commandDialect']
66
68
  /** The argument holding a filesystem path; see {@link ToolDefinition.pathArgument}. */
67
69
  pathArgument?: string
70
+ /** The argument holding a canonical URL; see {@link ToolDefinition.urlArgument}. */
71
+ urlArgument?: string
68
72
  /** The argument asking to leave the sandbox; see {@link ToolDefinition.sandboxEscapeArgument}. */
69
73
  sandboxEscapeArgument?: string
70
74
  execute(input: z.infer<S>, context: ToolContext): Promise<ToolResult>
@@ -117,6 +121,7 @@ export function defineTool<S extends z.ZodType>(
117
121
  ...(options.commandArgument !== undefined ? { commandArgument: options.commandArgument } : {}),
118
122
  ...(options.commandDialect !== undefined ? { commandDialect: options.commandDialect } : {}),
119
123
  ...(options.pathArgument !== undefined ? { pathArgument: options.pathArgument } : {}),
124
+ ...(options.urlArgument !== undefined ? { urlArgument: options.urlArgument } : {}),
120
125
  ...(options.sandboxEscapeArgument !== undefined
121
126
  ? { sandboxEscapeArgument: options.sandboxEscapeArgument }
122
127
  : {}),
@@ -136,6 +141,14 @@ export function defineTool<S extends z.ZodType>(
136
141
  ? options.destructive
137
142
  : constantDestructive(options.destructive as boolean),
138
143
  isConcurrencySafe: () => options.concurrencySafe,
144
+ ...(options.capturesScreen !== undefined
145
+ ? {
146
+ capturesScreen:
147
+ typeof options.capturesScreen === 'function'
148
+ ? options.capturesScreen
149
+ : () => options.capturesScreen as boolean,
150
+ }
151
+ : {}),
139
152
 
140
153
  async execute(input: TInput, context: ToolContext): Promise<ToolResult> {
141
154
  try {
@@ -0,0 +1,4 @@
1
+ export { buildScheduleTools, SCHEDULE_TOOL_NAME } from './schedule-tool.js'
2
+ export { buildSessionLoopTools, SESSION_LOOP_TOOL_NAME } from './loop-tool.js'
3
+ export { revealHiddenCharacters, scanSchedulePrompt } from './prompt-scan.js'
4
+ export type * from './types.js'