@namzu/sdk 45.0.0 → 45.1.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 (230) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/authorization/gate.d.ts.map +1 -1
  3. package/dist/authorization/gate.js +12 -2
  4. package/dist/authorization/gate.js.map +1 -1
  5. package/dist/authorization/rules.d.ts.map +1 -1
  6. package/dist/authorization/rules.js +37 -6
  7. package/dist/authorization/rules.js.map +1 -1
  8. package/dist/authorization/shell-lexer.d.ts +14 -0
  9. package/dist/authorization/shell-lexer.d.ts.map +1 -1
  10. package/dist/authorization/shell-lexer.js +19 -6
  11. package/dist/authorization/shell-lexer.js.map +1 -1
  12. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  13. package/dist/bridge/a2a/mapper.js +2 -0
  14. package/dist/bridge/a2a/mapper.js.map +1 -1
  15. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  16. package/dist/bridge/sse/mapper.js +1 -0
  17. package/dist/bridge/sse/mapper.js.map +1 -1
  18. package/dist/directory/types.d.ts +2 -0
  19. package/dist/directory/types.d.ts.map +1 -1
  20. package/dist/directory/types.js.map +1 -1
  21. package/dist/manager/resident/outbox.d.ts +4 -4
  22. package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
  23. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  24. package/dist/prompt/coding-agent-doctrine.js +1 -0
  25. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  26. package/dist/public-runtime.d.ts +4 -1
  27. package/dist/public-runtime.d.ts.map +1 -1
  28. package/dist/public-runtime.js +11 -1
  29. package/dist/public-runtime.js.map +1 -1
  30. package/dist/public-tools.d.ts +4 -0
  31. package/dist/public-tools.d.ts.map +1 -1
  32. package/dist/public-tools.js +10 -0
  33. package/dist/public-tools.js.map +1 -1
  34. package/dist/public-types.d.ts +7 -1
  35. package/dist/public-types.d.ts.map +1 -1
  36. package/dist/runtime/query/declined.d.ts +12 -0
  37. package/dist/runtime/query/declined.d.ts.map +1 -0
  38. package/dist/runtime/query/declined.js +12 -0
  39. package/dist/runtime/query/declined.js.map +1 -0
  40. package/dist/runtime/query/executor.d.ts +3 -1
  41. package/dist/runtime/query/executor.d.ts.map +1 -1
  42. package/dist/runtime/query/executor.js +14 -2
  43. package/dist/runtime/query/executor.js.map +1 -1
  44. package/dist/runtime/query/index.d.ts.map +1 -1
  45. package/dist/runtime/query/index.js +1 -0
  46. package/dist/runtime/query/index.js.map +1 -1
  47. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  48. package/dist/runtime/query/iteration/index.js +7 -0
  49. package/dist/runtime/query/iteration/index.js.map +1 -1
  50. package/dist/runtime/query/iteration/phases/context.d.ts +6 -0
  51. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  52. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  53. package/dist/runtime/query/iteration/phases/handoff.d.ts +22 -0
  54. package/dist/runtime/query/iteration/phases/handoff.d.ts.map +1 -0
  55. package/dist/runtime/query/iteration/phases/handoff.js +65 -0
  56. package/dist/runtime/query/iteration/phases/handoff.js.map +1 -0
  57. package/dist/runtime/query/iteration/phases/index.d.ts +1 -0
  58. package/dist/runtime/query/iteration/phases/index.d.ts.map +1 -1
  59. package/dist/runtime/query/iteration/phases/index.js +1 -0
  60. package/dist/runtime/query/iteration/phases/index.js.map +1 -1
  61. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  62. package/dist/runtime/query/iteration/phases/tool-review.js +8 -3
  63. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  64. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  65. package/dist/runtime/query/resume-pending.js +3 -2
  66. package/dist/runtime/query/resume-pending.js.map +1 -1
  67. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  68. package/dist/runtime/query/review-policy.js +4 -3
  69. package/dist/runtime/query/review-policy.js.map +1 -1
  70. package/dist/schedules/cron.d.ts +21 -0
  71. package/dist/schedules/cron.d.ts.map +1 -0
  72. package/dist/schedules/cron.js +167 -0
  73. package/dist/schedules/cron.js.map +1 -0
  74. package/dist/schedules/describe.d.ts +14 -0
  75. package/dist/schedules/describe.d.ts.map +1 -0
  76. package/dist/schedules/describe.js +133 -0
  77. package/dist/schedules/describe.js.map +1 -0
  78. package/dist/schedules/errors.d.ts +11 -0
  79. package/dist/schedules/errors.d.ts.map +1 -0
  80. package/dist/schedules/errors.js +15 -0
  81. package/dist/schedules/errors.js.map +1 -0
  82. package/dist/schedules/evaluate.d.ts +35 -0
  83. package/dist/schedules/evaluate.d.ts.map +1 -0
  84. package/dist/schedules/evaluate.js +158 -0
  85. package/dist/schedules/evaluate.js.map +1 -0
  86. package/dist/schedules/index.d.ts +11 -0
  87. package/dist/schedules/index.d.ts.map +1 -0
  88. package/dist/schedules/index.js +8 -0
  89. package/dist/schedules/index.js.map +1 -0
  90. package/dist/schedules/next-fire.d.ts +50 -0
  91. package/dist/schedules/next-fire.d.ts.map +1 -0
  92. package/dist/schedules/next-fire.js +250 -0
  93. package/dist/schedules/next-fire.js.map +1 -0
  94. package/dist/schedules/spec.d.ts +30 -0
  95. package/dist/schedules/spec.d.ts.map +1 -0
  96. package/dist/schedules/spec.js +169 -0
  97. package/dist/schedules/spec.js.map +1 -0
  98. package/dist/schedules/types.d.ts +144 -0
  99. package/dist/schedules/types.d.ts.map +1 -0
  100. package/dist/schedules/types.js +11 -0
  101. package/dist/schedules/types.js.map +1 -0
  102. package/dist/schedules/tz.d.ts +44 -0
  103. package/dist/schedules/tz.d.ts.map +1 -0
  104. package/dist/schedules/tz.js +141 -0
  105. package/dist/schedules/tz.js.map +1 -0
  106. package/dist/skills/index.d.ts +1 -1
  107. package/dist/skills/index.d.ts.map +1 -1
  108. package/dist/skills/index.js +1 -1
  109. package/dist/skills/index.js.map +1 -1
  110. package/dist/skills/loader.d.ts +16 -3
  111. package/dist/skills/loader.d.ts.map +1 -1
  112. package/dist/skills/loader.js +48 -4
  113. package/dist/skills/loader.js.map +1 -1
  114. package/dist/tools/builtins/browser-url.d.ts +83 -0
  115. package/dist/tools/builtins/browser-url.d.ts.map +1 -0
  116. package/dist/tools/builtins/browser-url.js +240 -0
  117. package/dist/tools/builtins/browser-url.js.map +1 -0
  118. package/dist/tools/builtins/browser.d.ts +367 -0
  119. package/dist/tools/builtins/browser.d.ts.map +1 -0
  120. package/dist/tools/builtins/browser.js +704 -0
  121. package/dist/tools/builtins/browser.js.map +1 -0
  122. package/dist/tools/builtins/skill.d.ts.map +1 -1
  123. package/dist/tools/builtins/skill.js +15 -0
  124. package/dist/tools/builtins/skill.js.map +1 -1
  125. package/dist/tools/defineTool.d.ts +2 -0
  126. package/dist/tools/defineTool.d.ts.map +1 -1
  127. package/dist/tools/defineTool.js +1 -0
  128. package/dist/tools/defineTool.js.map +1 -1
  129. package/dist/tools/schedules/index.d.ts +5 -0
  130. package/dist/tools/schedules/index.d.ts.map +1 -0
  131. package/dist/tools/schedules/index.js +4 -0
  132. package/dist/tools/schedules/index.js.map +1 -0
  133. package/dist/tools/schedules/loop-tool.d.ts +14 -0
  134. package/dist/tools/schedules/loop-tool.d.ts.map +1 -0
  135. package/dist/tools/schedules/loop-tool.js +81 -0
  136. package/dist/tools/schedules/loop-tool.js.map +1 -0
  137. package/dist/tools/schedules/present.d.ts +16 -0
  138. package/dist/tools/schedules/present.d.ts.map +1 -0
  139. package/dist/tools/schedules/present.js +69 -0
  140. package/dist/tools/schedules/present.js.map +1 -0
  141. package/dist/tools/schedules/prompt-scan.d.ts +17 -0
  142. package/dist/tools/schedules/prompt-scan.d.ts.map +1 -0
  143. package/dist/tools/schedules/prompt-scan.js +92 -0
  144. package/dist/tools/schedules/prompt-scan.js.map +1 -0
  145. package/dist/tools/schedules/schedule-tool.d.ts +16 -0
  146. package/dist/tools/schedules/schedule-tool.d.ts.map +1 -0
  147. package/dist/tools/schedules/schedule-tool.js +327 -0
  148. package/dist/tools/schedules/schedule-tool.js.map +1 -0
  149. package/dist/tools/schedules/types.d.ts +184 -0
  150. package/dist/tools/schedules/types.d.ts.map +1 -0
  151. package/dist/tools/schedules/types.js +11 -0
  152. package/dist/tools/schedules/types.js.map +1 -0
  153. package/dist/types/authorization/index.d.ts +86 -9
  154. package/dist/types/authorization/index.d.ts.map +1 -1
  155. package/dist/types/authorization/index.js +11 -1
  156. package/dist/types/authorization/index.js.map +1 -1
  157. package/dist/types/browser/index.d.ts +280 -0
  158. package/dist/types/browser/index.d.ts.map +1 -0
  159. package/dist/types/browser/index.js +12 -0
  160. package/dist/types/browser/index.js.map +1 -0
  161. package/dist/types/session/events.d.ts +6 -0
  162. package/dist/types/session/events.d.ts.map +1 -1
  163. package/dist/types/session/events.js.map +1 -1
  164. package/dist/types/session/records.d.ts +23 -0
  165. package/dist/types/session/records.d.ts.map +1 -1
  166. package/dist/types/session/records.js +9 -0
  167. package/dist/types/session/records.js.map +1 -1
  168. package/dist/types/tool/index.d.ts +41 -0
  169. package/dist/types/tool/index.d.ts.map +1 -1
  170. package/dist/types/tool/index.js.map +1 -1
  171. package/dist/types/tool/presentation.d.ts +7 -0
  172. package/dist/types/tool/presentation.d.ts.map +1 -1
  173. package/dist/utils/frontmatter.d.ts +18 -2
  174. package/dist/utils/frontmatter.d.ts.map +1 -1
  175. package/dist/utils/frontmatter.js +13 -3
  176. package/dist/utils/frontmatter.js.map +1 -1
  177. package/dist/utils/id.d.ts +8 -0
  178. package/dist/utils/id.d.ts.map +1 -1
  179. package/dist/utils/id.js +12 -0
  180. package/dist/utils/id.js.map +1 -1
  181. package/package.json +1 -1
  182. package/src/authorization/gate.ts +14 -2
  183. package/src/authorization/rules.ts +37 -7
  184. package/src/authorization/shell-lexer.ts +36 -6
  185. package/src/bridge/a2a/mapper.ts +2 -0
  186. package/src/bridge/sse/mapper.ts +1 -0
  187. package/src/directory/types.ts +2 -0
  188. package/src/prompt/coding-agent-doctrine.ts +1 -0
  189. package/src/public-runtime.ts +28 -0
  190. package/src/public-tools.ts +35 -0
  191. package/src/public-types.ts +50 -0
  192. package/src/runtime/query/declined.ts +12 -0
  193. package/src/runtime/query/executor.ts +17 -1
  194. package/src/runtime/query/index.ts +1 -0
  195. package/src/runtime/query/iteration/index.ts +8 -0
  196. package/src/runtime/query/iteration/phases/context.ts +6 -0
  197. package/src/runtime/query/iteration/phases/handoff.ts +74 -0
  198. package/src/runtime/query/iteration/phases/index.ts +1 -0
  199. package/src/runtime/query/iteration/phases/tool-review.ts +11 -4
  200. package/src/runtime/query/resume-pending.ts +3 -2
  201. package/src/runtime/query/review-policy.ts +4 -3
  202. package/src/schedules/cron.ts +202 -0
  203. package/src/schedules/describe.ts +138 -0
  204. package/src/schedules/errors.ts +15 -0
  205. package/src/schedules/evaluate.ts +178 -0
  206. package/src/schedules/index.ts +18 -0
  207. package/src/schedules/next-fire.ts +257 -0
  208. package/src/schedules/spec.ts +210 -0
  209. package/src/schedules/types.ts +163 -0
  210. package/src/schedules/tz.ts +155 -0
  211. package/src/skills/index.ts +1 -1
  212. package/src/skills/loader.ts +55 -4
  213. package/src/tools/builtins/browser-url.ts +251 -0
  214. package/src/tools/builtins/browser.ts +817 -0
  215. package/src/tools/builtins/skill.ts +18 -0
  216. package/src/tools/defineTool.ts +3 -0
  217. package/src/tools/schedules/index.ts +4 -0
  218. package/src/tools/schedules/loop-tool.ts +85 -0
  219. package/src/tools/schedules/present.ts +86 -0
  220. package/src/tools/schedules/prompt-scan.ts +96 -0
  221. package/src/tools/schedules/schedule-tool.ts +376 -0
  222. package/src/tools/schedules/types.ts +197 -0
  223. package/src/types/authorization/index.ts +60 -2
  224. package/src/types/browser/index.ts +341 -0
  225. package/src/types/session/events.ts +6 -0
  226. package/src/types/session/records.ts +10 -0
  227. package/src/types/tool/index.ts +42 -0
  228. package/src/types/tool/presentation.ts +7 -0
  229. package/src/utils/frontmatter.ts +29 -3
  230. package/src/utils/id.ts +14 -0
@@ -57,15 +57,27 @@ export function describeRule(rule: AuthorizationRule): string {
57
57
  // Names the argument, not just the pattern. That is what tells a
58
58
  // model whether a different value could get through — which is the
59
59
  // difference between rewording once and rewording forever.
60
- const verb = rule.decision === 'deny' ? 'denied' : 'allowed'
60
+ const verb =
61
+ rule.decision === 'deny'
62
+ ? 'denied'
63
+ : rule.decision === 'review'
64
+ ? 'sent for review'
65
+ : 'allowed'
61
66
  return `${verb} because the \`${rule.argument}\` argument matched ${rule.pattern} (this rule applies to ${rule.toolNames.join(', ')})`
62
67
  }
63
68
 
64
69
  case 'custom_pattern': {
65
70
  const where = rule.target === 'both' ? 'name or arguments' : rule.target
66
- const verb = rule.decision === 'deny' ? 'denied' : 'allowed'
71
+ const verb =
72
+ rule.decision === 'deny'
73
+ ? 'denied'
74
+ : rule.decision === 'review'
75
+ ? 'sent for review'
76
+ : 'allowed'
67
77
  return `${verb} by a pattern rule matching the ${where}: ${rule.pattern}`
68
78
  }
79
+ case 'predicate':
80
+ return rule.description
69
81
  default: {
70
82
  const exhaustive: never = rule
71
83
  return `matched an unrecognised rule: ${JSON.stringify(exhaustive)}`
@@ -134,19 +134,33 @@ export function evaluateRule(
134
134
  // A command line is not one string, and testing it as one is how a
135
135
  // prohibition gets bypassed: `^git push` sees `git push origin main`
136
136
  // and does not see `true; git push origin main`. See
137
- // `decomposeCommandLine` for the measurement and for why the two
137
+ // `decomposeCommandLine` for the measurement and for why the
138
138
  // decisions must read the result differently.
139
- const { segments, opaque } = decomposeCommandLine(subject, dialect)
140
-
141
- if (rule.decision === 'deny') {
139
+ //
140
+ // An argument the tool declares as a canonical URL is not a command
141
+ // line, and cutting it at `&` would make an `allow` for a site
142
+ // decline every address with a query string. See
143
+ // `ToolDefinition.urlArgument`.
144
+ const isUrl = toolDef?.urlArgument === rule.argument
145
+ const { segments, opaque } = isUrl
146
+ ? { segments: [subject], opaque: false }
147
+ : decomposeCommandLine(subject, dialect)
148
+
149
+ if (rule.decision === 'deny' || rule.decision === 'review') {
142
150
  // ANY segment. The whole subject is tested first so an
143
151
  // unanchored deny keeps matching across a boundary, which
144
152
  // splitting alone would have taken away. Then each command's
145
153
  // words as bash passes them, so that `'git' push` is `git push`.
146
- if (compiledPattern.test(subject)) return 'deny'
147
- if (segments.some((segment) => compiledPattern.test(segment))) return 'deny'
154
+ //
155
+ // `review` reads like `deny`, because it is a restriction too: a
156
+ // rule that asks before `git push` must ask before
157
+ // `true; git push` as well. Matching too much costs a prompt;
158
+ // matching too little skips the question the operator asked for.
159
+ if (compiledPattern.test(subject)) return rule.decision
160
+ if (segments.some((segment) => compiledPattern.test(segment))) return rule.decision
161
+ if (isUrl) return null
148
162
  return decodedCommands(subject, dialect).some((text) => compiledPattern.test(text))
149
- ? 'deny'
163
+ ? rule.decision
150
164
  : null
151
165
  }
152
166
 
@@ -165,6 +179,22 @@ export function evaluateRule(
165
179
  return null
166
180
  }
167
181
 
182
+ case 'predicate': {
183
+ // A rule that throws has not decided the call is safe. Reading the
184
+ // exception as "no opinion" would let the next rule, or the mode,
185
+ // approve what this one was written to refuse.
186
+ try {
187
+ return rule.decide({
188
+ toolName,
189
+ toolInput,
190
+ toolDef,
191
+ commandDialect: dialect,
192
+ })
193
+ } catch {
194
+ return 'deny'
195
+ }
196
+ }
197
+
168
198
  default: {
169
199
  const _exhaustive: never = rule
170
200
  throw new Error(`Unhandled verification rule type: ${(_exhaustive as { type: string }).type}`)
@@ -83,6 +83,13 @@ export interface ShellRedirection {
83
83
  readonly fd?: string
84
84
  /** The target word. For a here-document, its delimiter. */
85
85
  readonly target: ShellWord
86
+ /**
87
+ * A here-document's body as written, up to its delimiter line, once the
88
+ * lexer has read it: the text the command reads on its input, which is
89
+ * a command line of its own when the command is a shell (`bash <<EOF`).
90
+ * Absent for every other operator and for a body the line never reached.
91
+ */
92
+ readonly body?: string
86
93
  }
87
94
 
88
95
  /** One simple command. */
@@ -118,6 +125,13 @@ export interface ShellLexResult {
118
125
  * (`{ a; } > f`) that belong to no single simple command.
119
126
  */
120
127
  readonly redirections: readonly ShellRedirection[]
128
+ /**
129
+ * Words that belong to no simple command: a `for` or `select` loop's
130
+ * variable and the words of its list, and a `case` statement's subject
131
+ * and patterns. `for d in ~/x; do rm -r "$d"; done` passes `~/x` to `rm`
132
+ * although no command lists it.
133
+ */
134
+ readonly compoundWords: readonly ShellWord[]
121
135
  /** True when {@link commands} may not be everything the line runs. */
122
136
  readonly opaque: boolean
123
137
  /** False when parsing stopped early: a syntax error or an unsupported construct. */
@@ -169,6 +183,7 @@ export function lexShellCommandLine(line: string, options: ShellLexOptions = {})
169
183
  return {
170
184
  commands: context.commands,
171
185
  redirections: context.redirections,
186
+ compoundWords: context.compoundWords,
172
187
  opaque: context.reasons.size > 0,
173
188
  complete: context.complete,
174
189
  reasons: [...context.reasons],
@@ -178,6 +193,7 @@ export function lexShellCommandLine(line: string, options: ShellLexOptions = {})
178
193
  class Context {
179
194
  readonly commands: ShellCommand[] = []
180
195
  readonly redirections: ShellRedirection[] = []
196
+ readonly compoundWords: ShellWord[] = []
181
197
  readonly reasons = new Set<string>()
182
198
  complete = true
183
199
  /**
@@ -358,6 +374,8 @@ interface PendingHeredoc {
358
374
  readonly delimiter: string
359
375
  readonly stripTabs: boolean
360
376
  readonly quoted: boolean
377
+ /** The redirection the body belongs to, filled in when it is read. */
378
+ readonly redirection: { body?: string }
361
379
  }
362
380
 
363
381
  type Last =
@@ -790,6 +808,7 @@ class Parser {
790
808
  }
791
809
  const name = this.takePlainWord()
792
810
  if (name.kind !== 'word') throw this.unexpected(name)
811
+ this.context.compoundWords.push(name.word)
793
812
  this.newlines()
794
813
  token = this.peek()
795
814
  if (token.kind === 'word' && !token.word.quoted && token.word.value === 'in') {
@@ -807,7 +826,10 @@ class Parser {
807
826
  }
808
827
  for (;;) {
809
828
  token = this.takePlainWord()
810
- if (token.kind === 'word') continue
829
+ if (token.kind === 'word') {
830
+ this.context.compoundWords.push(token.word)
831
+ continue
832
+ }
811
833
  if (token.kind === 'newline' || (token.kind === 'op' && token.op === ';')) break
812
834
  throw this.unexpected(token)
813
835
  }
@@ -854,6 +876,7 @@ class Parser {
854
876
  this.take()
855
877
  const subject = this.takePlainWord()
856
878
  if (subject.kind !== 'word') throw this.unexpected(subject)
879
+ this.context.compoundWords.push(subject.word)
857
880
  this.newlines()
858
881
  const keyword = this.take()
859
882
  if (keyword.kind !== 'word' || keyword.word.quoted || keyword.word.value !== 'in') {
@@ -887,6 +910,7 @@ class Parser {
887
910
  patterns()
888
911
  const pattern = this.take()
889
912
  if (pattern.kind !== 'word') throw this.unexpected(pattern)
913
+ this.context.compoundWords.push(pattern.word)
890
914
  const next = this.take()
891
915
  if (next.kind === 'op' && next.op === '|') continue
892
916
  if (next.kind === 'op' && next.op === ')') break
@@ -1114,6 +1138,11 @@ class Parser {
1114
1138
  // Ubuntu ship).
1115
1139
  this.context.opaque('quoted or expanding target of >& or <&')
1116
1140
  }
1141
+ const redirection: { -readonly [K in keyof ShellRedirection]: ShellRedirection[K] } = {
1142
+ operator: operator.op,
1143
+ ...(operator.fd !== undefined ? { fd: operator.fd } : {}),
1144
+ target: target.word,
1145
+ }
1117
1146
  if (operator.op === '<<' || operator.op === '<<-') {
1118
1147
  if (target.word.value.includes('\n')) {
1119
1148
  // Bash starts the body at the newline inside the delimiter, so
@@ -1125,13 +1154,10 @@ class Parser {
1125
1154
  delimiter: target.word.value,
1126
1155
  stripTabs: operator.op === '<<-',
1127
1156
  quoted: target.word.quoted,
1157
+ redirection,
1128
1158
  })
1129
1159
  }
1130
- return {
1131
- operator: operator.op,
1132
- ...(operator.fd !== undefined ? { fd: operator.fd } : {}),
1133
- target: target.word,
1134
- }
1160
+ return redirection
1135
1161
  }
1136
1162
 
1137
1163
  /**
@@ -1641,7 +1667,9 @@ class Parser {
1641
1667
  const heredoc = this.heredocs.shift() as PendingHeredoc
1642
1668
  let i = this.pos
1643
1669
  const bodyStart = i
1670
+ let bodyEnd = i
1644
1671
  for (;;) {
1672
+ bodyEnd = i
1645
1673
  if (i >= src.length) break
1646
1674
  let line = ''
1647
1675
  let j = i
@@ -1664,7 +1692,9 @@ class Parser {
1664
1692
  const test = heredoc.stripTabs ? line.replace(/^\t+/, '') : line
1665
1693
  i = j
1666
1694
  if (test === heredoc.delimiter) break
1695
+ bodyEnd = i
1667
1696
  }
1697
+ heredoc.redirection.body = src.slice(bodyStart, bodyEnd)
1668
1698
  if (!heredoc.quoted) {
1669
1699
  const body = src.slice(bodyStart, i)
1670
1700
  if (/\$[({[]|`/.test(body)) this.heredocBody(body)
@@ -227,6 +227,8 @@ const MAPPING: {
227
227
  // it whether and when a retry is justified.
228
228
  {
229
229
  checkpointId: e.checkpointId,
230
+ // A person, not a retry, is what this pause waits for.
231
+ ...(e.handoff ? { handoff: e.handoff } : {}),
230
232
  ...(e.failure
231
233
  ? {
232
234
  code: e.failure.code,
@@ -297,6 +297,7 @@ const MAPPING: {
297
297
  ...(e.failure ? { failure: e.failure } : {}),
298
298
  ...(e.providerError ? { provider_error: e.providerError } : {}),
299
299
  ...(e.explanation ? { explanation: e.explanation } : {}),
300
+ ...(e.handoff ? { handoff: e.handoff } : {}),
300
301
  }),
301
302
  },
302
303
 
@@ -16,6 +16,8 @@ import type { ToolDefinition } from '../types/tool/index.js'
16
16
  * those is a `major` waiting to happen on a published type. The layering
17
17
  * question — does a convention belong outside the kernel — was answered yes
18
18
  * and stands; the shape question had simply never been asked.
19
+ * Schedules were later answered outside this loader, with a zone per spec and
20
+ * a claimed occurrence key: see `docs/sdk/schedules.md` (`../schedules/`).
19
21
  */
20
22
  export type DirectorySlot = 'agent' | 'instructions' | 'tools' | 'skills' | 'agents'
21
23
 
@@ -69,6 +69,7 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
69
69
  - Before any command that could discard uncommitted work, run \`git status\`. If there are changes there that you did not make, stop and ask rather than stashing, committing or discarding somebody else's work.
70
70
  - Commit only when the user asks, on the branch that is checked out. Do not create or switch branches unless the user asks or the project's instructions require it.
71
71
  - After a broad \`git add\`, review what was staged before committing; a file whose name looks harmless can still carry a secret.
72
+ - Never change git configuration or other persistent settings without asking: not \`user.name\` or \`user.email\`, hooks, remotes, credential helpers, or any \`git config\` (local or global), nor a shell profile or a tool's own config file. If a commit fails because no identity is set, stop and tell the user the command to set one; do not invent one.
72
73
 
73
74
  ### Keeping the user informed
74
75
  - Before a batch of tool calls, say in one short line what you are about to do and why. When you have been working for a while without saying anything, say in a few words where you are, then continue. One line, not a paragraph; the tool rows on screen already show the details.
@@ -423,6 +423,7 @@ export {
423
423
  discoverSkills,
424
424
  loadSkill,
425
425
  resolveSkillChain,
426
+ SKILL_FRONTMATTER_KEYS,
426
427
  SkillRegistry,
427
428
  } from './skills/index.js'
428
429
  // The one frontmatter reader. `loadSkill` is built on it, and a host reading
@@ -631,6 +632,26 @@ export {
631
632
  formatCompletionNotification,
632
633
  } from './scheduler/completion-inbox.js'
633
634
 
635
+ // Scheduled jobs: WHEN something is due, and nothing about how a host stores
636
+ // or runs a job. The time engine and the evaluator are pure (`Intl` for time
637
+ // zones, no clock, no I/O); the CLI keeps its job files and history to itself.
638
+ export {
639
+ countOccurrences,
640
+ describeSchedule,
641
+ evaluateJob,
642
+ hostTimeZone,
643
+ nextFireTime,
644
+ parseCronExpression,
645
+ parseDuration,
646
+ parseScheduleSpec,
647
+ previousFireTime,
648
+ SCHEDULE_CATCH_UP_WINDOW_MS,
649
+ SCHEDULE_LATE_GRACE_MS,
650
+ ScheduleValidationError,
651
+ upcomingFireTimes,
652
+ validateTimeZone,
653
+ } from './schedules/index.js'
654
+
634
655
  // ─── providers, sandbox, vault ───────────────────────────────────────────
635
656
 
636
657
  export {
@@ -899,6 +920,11 @@ export {
899
920
  AuthorizationGate,
900
921
  } from './authorization/index.js'
901
922
 
923
+ // The one reader of a bash command line the gate itself uses. A host that
924
+ // writes a `predicate` rule about what a line runs decides on this reading,
925
+ // so its rule and the SDK's own never disagree about where a quote ends.
926
+ export { lexShellCommandLine } from './authorization/shell-lexer.js'
927
+
902
928
  // NZ-BOOT-03: the module-attributed invariant registry. `compaction.ts` and
903
929
  // `claim-disk.ts` register themselves against the shared `invariants`
904
930
  // instance at import time (see each file); `namzu doctor` and any host can
@@ -1283,6 +1309,8 @@ export {
1283
1309
  isReviewExempt,
1284
1310
  isReviewMode,
1285
1311
  } from './runtime/query/review-policy.js'
1312
+ // What the model is told when a person declines a call without words of their own.
1313
+ export { DECLINED_TOOL_CALL_FEEDBACK } from './runtime/query/declined.js'
1286
1314
 
1287
1315
  // The system prompt is open: a contribution registry the assembler
1288
1316
  // consumes, with skills as its first contributor. See `prompt/contributions.ts`.
@@ -107,6 +107,29 @@ export {
107
107
  COMPUTER_USE_TOOL_NAME,
108
108
  createComputerUseTool,
109
109
  } from './tools/builtins/computer-use.js'
110
+ // The browser contract: the two tools over a host a separate package
111
+ // provides, and the canonicalisers a host and a site-rule compiler must share
112
+ // with the tools so every party reads one spelling of an address.
113
+ export {
114
+ BROWSER_ACT_TOOL_NAME,
115
+ BROWSER_TOOL_NAME,
116
+ browserHostErrorOf,
117
+ createBrowserTools,
118
+ formatBrowserPageHeader,
119
+ isBrowserCallReadOnly,
120
+ } from './tools/builtins/browser.js'
121
+ export {
122
+ BROWSER_URL_MAX_LENGTH,
123
+ canonicalizeBrowserOrigin,
124
+ canonicalizeBrowserSitePattern,
125
+ canonicalizeBrowserUrl,
126
+ isCloudMetadataHost,
127
+ } from './tools/builtins/browser-url.js'
128
+ export {
129
+ BROWSER_FILL_FORM_MAX_FIELDS,
130
+ BROWSER_SNAPSHOT_MAX_CHARS,
131
+ BROWSER_WAIT_MAX_MS,
132
+ } from './types/browser/index.js'
110
133
 
111
134
  // ─── Domain tool builders ────────────────────────────────────────────────
112
135
 
@@ -129,6 +152,18 @@ export {
129
152
  } from './tools/coordinator/ask-user-question.js'
130
153
  export { buildAgentTool, type AgentToolOptions } from './tools/coordinator/agent.js'
131
154
 
155
+ // Scheduled jobs and in-session loops, over host callbacks: the host stores
156
+ // jobs, draws the confirmation and computes every field it shows. Register
157
+ // only where a person can confirm (never headless, scheduled or delegated).
158
+ export {
159
+ buildScheduleTools,
160
+ buildSessionLoopTools,
161
+ revealHiddenCharacters,
162
+ scanSchedulePrompt,
163
+ SCHEDULE_TOOL_NAME,
164
+ SESSION_LOOP_TOOL_NAME,
165
+ } from './tools/schedules/index.js'
166
+
132
167
  // ─── RAG tool builder ────────────────────────────────────────────────────
133
168
 
134
169
  export { createRAGTool } from './rag/index.js'
@@ -62,6 +62,13 @@ export type * from './types/sandbox/index.js'
62
62
  export type * from './types/structured-output/index.js'
63
63
  export type * from './types/invocation/index.js'
64
64
  export type * from './types/computer-use/index.js'
65
+ export type * from './types/browser/index.js'
66
+ export type { BrowserActToolInput, BrowserToolInput } from './tools/builtins/browser.js'
67
+ export type {
68
+ BrowserOriginVerdict,
69
+ BrowserSitePatternVerdict,
70
+ BrowserUrlVerdict,
71
+ } from './tools/builtins/browser-url.js'
65
72
  export type * from './types/authorization/index.js'
66
73
  export type * from './types/bus/index.js'
67
74
  export type * from './types/probe/index.js'
@@ -115,6 +122,7 @@ export type { PricingSubject } from './manager/session/turn-recorder.js'
115
122
  export type {
116
123
  FrontmatterOptions,
117
124
  FrontmatterValue,
125
+ ParseFrontmatterOptions,
118
126
  ParsedFrontmatter,
119
127
  } from './utils/frontmatter.js'
120
128
  export type {
@@ -262,6 +270,13 @@ export type { AgentBusConfig } from './bus/index.js'
262
270
  export type { ToolCallContext } from './authorization/index.js'
263
271
  export type { PermissionPreset } from './authorization/index.js'
264
272
  export type { EvaluateRuleOptions } from './authorization/rules.js'
273
+ export type {
274
+ ShellCommand,
275
+ ShellLexOptions,
276
+ ShellLexResult,
277
+ ShellRedirection,
278
+ ShellWord,
279
+ } from './authorization/shell-lexer.js'
265
280
 
266
281
  export type {
267
282
  DiskSessionStoreConfig,
@@ -629,3 +644,38 @@ export type {
629
644
  ParsedSessionLogLine,
630
645
  SessionLogLineFault,
631
646
  } from './session/log-hash.js'
647
+
648
+ // Scheduled jobs: the time engine's and evaluator's types. The unions here
649
+ // may grow in a minor release; switch over them with a `default:` branch.
650
+ export type {
651
+ CronExpression,
652
+ DescribeScheduleOptions,
653
+ OccurrenceCount,
654
+ ParseScheduleOptions,
655
+ ScheduleAtSpec,
656
+ ScheduleCronSpec,
657
+ ScheduleDecision,
658
+ ScheduleEvaluationInput,
659
+ ScheduleEvaluationJob,
660
+ ScheduleEvaluationState,
661
+ ScheduleEverySpec,
662
+ ScheduleFireTrigger,
663
+ ScheduleJobLifecycle,
664
+ ScheduleMissedReason,
665
+ ScheduleObservedGap,
666
+ ScheduleSkipReason,
667
+ ScheduleSpec,
668
+ } from './schedules/index.js'
669
+ export type {
670
+ ScheduleBrowserGrant,
671
+ ScheduleBrowserSiteLevel,
672
+ ScheduleConfirmAnswer,
673
+ ScheduleConfirmRequest,
674
+ ScheduleJobDraft,
675
+ ScheduleJobPreview,
676
+ ScheduleJobSummary,
677
+ ScheduleRuleEffect,
678
+ ScheduleToolHost,
679
+ SessionLoop,
680
+ SessionLoopHost,
681
+ } from './tools/schedules/index.js'
@@ -0,0 +1,12 @@
1
+ /**
2
+ * What the model is told when a person declines its tool calls and gave no
3
+ * words of their own.
4
+ *
5
+ * "Declined" alone reads to a model as an obstacle to route around: in a live
6
+ * session an operator refused a browser navigation and the model fetched the
7
+ * same page through web search instead. The refusal is about the outcome, not
8
+ * the tool, so the text says so — for every tool, since any of them can be
9
+ * swapped for another that reaches the same place.
10
+ */
11
+ export const DECLINED_TOOL_CALL_FEEDBACK =
12
+ 'The user declined this. Do not get the same content or result another way (another tool, another site or address, or a web search) unless you ask the user first and they agree. Say what you wanted to do, or carry on without it.'
@@ -39,6 +39,7 @@ import type {
39
39
  SkillRegistryRef,
40
40
  ToolContext,
41
41
  ToolDispatchOptions,
42
+ ToolHandoff,
42
43
  ToolRegistryContract,
43
44
  ToolResult,
44
45
  } from '../../types/tool/index.js'
@@ -489,6 +490,8 @@ export interface ToolCallOutcome {
489
490
  /** Rich form for the model, when the tool supplied one. */
490
491
  content?: ToolResultContent
491
492
  isError?: boolean
493
+ /** The tool asked for a person; see `ToolResult.handoff`. */
494
+ handoff?: ToolHandoff
492
495
  }
493
496
 
494
497
  export interface ToolExecutionBatch {
@@ -1446,9 +1449,16 @@ export class ToolExecutor {
1446
1449
  }
1447
1450
 
1448
1451
  private resultPresentation(name: string, input: unknown, result: ToolResult) {
1449
- if (!result.success) return {}
1450
1452
  try {
1451
1453
  const view = this.config.tools.get(name)?.presentResult?.(input, result)
1454
+ // A failed call carries one view only: the person's No on the tool's
1455
+ // own screen, which a host draws as cancelled rather than failed and
1456
+ // cannot tell apart from the result text alone.
1457
+ if (!result.success) {
1458
+ return view?.kind === 'generic' && view.outcome === 'cancelled'
1459
+ ? { presentation: { kind: 'generic', label: view.label, outcome: 'cancelled' } as const }
1460
+ : {}
1461
+ }
1452
1462
  if (view?.kind !== 'diff') return {}
1453
1463
  const serialized = JSON.stringify(view)
1454
1464
  if (serialized.length > (this.config.maxToolOutputChars ?? DEFAULT_MAX_TOOL_OUTPUT_CHARS))
@@ -2004,6 +2014,12 @@ export class ToolExecutor {
2004
2014
  // a result whose image is unaffected — and a hook that needs it gone
2005
2015
  // says so with `content`, which wins over both.
2006
2016
  ...(modelContent !== undefined ? { content: modelContent } : {}),
2017
+ // Carried whatever a post-tool hook did to the text: the request is
2018
+ // the tool's statement about the world, not about its output, and a
2019
+ // hook that redacts a sign-in page has not signed anyone in.
2020
+ ...(result.handoff !== undefined && !this.config.abortSignal.aborted
2021
+ ? { handoff: result.handoff }
2022
+ : {}),
2007
2023
  }
2008
2024
  }
2009
2025
 
@@ -1462,6 +1462,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
1462
1462
  provider: resilientProvider,
1463
1463
  providerCapabilities: capabilities,
1464
1464
  strictCapabilities: params.strictCapabilities === true,
1465
+ ...(params.parentSessionId !== undefined ? { delegated: true } : {}),
1465
1466
  servingMember: () => serving.current,
1466
1467
  turnConfig,
1467
1468
  ...(params.stopWhen ? { stopWhen: params.stopWhen } : {}),
@@ -71,6 +71,7 @@ import {
71
71
  relieveOverflow,
72
72
  runCompactionCheck,
73
73
  } from './phases/compaction.js'
74
+ import { runHandoffPause } from './phases/handoff.js'
74
75
  import type { IterationContext } from './phases/index.js'
75
76
  import { runPlanGate } from './phases/plan.js'
76
77
  import { runToolReview } from './phases/tool-review.js'
@@ -1421,6 +1422,13 @@ export class IterationOrchestrator {
1421
1422
  return
1422
1423
  }
1423
1424
 
1425
+ // A tool asked for a person. The whole batch is committed; the
1426
+ // turn parks here rather than asking the model what to do about
1427
+ // something only the operator can do.
1428
+ if ((yield* runHandoffPause(this.ctx, iterationNum, reviewOutcome.results)) === 'stop') {
1429
+ return
1430
+ }
1431
+
1424
1432
  if (reviewOutcome.decision === 'rejected') {
1425
1433
  continue
1426
1434
  }
@@ -44,6 +44,12 @@ import type { ToolGrantSet } from '../../tool-grants.js'
44
44
 
45
45
  export interface IterationContext {
46
46
  readonly provider: LLMProvider
47
+ /**
48
+ * The turn runs in a delegated child session. A tool's request for a
49
+ * person then fails the turn instead of pausing it: nobody is watching
50
+ * a child to resume it, and its parent is waiting on a result.
51
+ */
52
+ readonly delegated?: boolean
47
53
  /** Driver-level request shapes negotiated for this turn. */
48
54
  readonly providerCapabilities?: ResolvedProviderCapabilities
49
55
  /** Refuse a capability mismatch instead of emitting a warning and degrading. */
@@ -0,0 +1,74 @@
1
+ import { GENAI, NAMZU } from '../../../../telemetry/attributes.js'
2
+ import { NamzuError } from '../../../../types/errors/index.js'
3
+ import type { SessionEvent } from '../../../../types/session/index.js'
4
+ import type { ToolCallOutcome } from '../../executor.js'
5
+ import type { IterationContext, PhaseSignal } from './context.js'
6
+
7
+ /**
8
+ * A tool asked for a person: stop before the next model call.
9
+ *
10
+ * Runs after the batch settled, so every result — the one that asked and
11
+ * its siblings — is already in the transcript and queued for the session
12
+ * log. The checkpoint written here is taken from that state, which is what
13
+ * lets a resume continue exactly as it does after a provider pause: the
14
+ * next step is a model call that sees the results.
15
+ *
16
+ * The first request in the batch speaks for it. Two tools that both need a
17
+ * person need the same thing from the operator — to come and look — and
18
+ * one pause answers both.
19
+ *
20
+ * In a delegated child the turn fails with the reason instead. Nobody
21
+ * resumes a child's turn: its parent is waiting on a result, and a failed
22
+ * child with the reason in it is a result the parent's model can act on.
23
+ */
24
+ export async function* runHandoffPause(
25
+ ctx: IterationContext,
26
+ iterationNum: number,
27
+ results: readonly ToolCallOutcome[],
28
+ ): AsyncGenerator<SessionEvent, PhaseSignal> {
29
+ const requested = results.find((result) => result.handoff !== undefined)
30
+ const handoff = requested?.handoff
31
+ if (!requested || !handoff) return 'continue'
32
+
33
+ if (ctx.delegated) {
34
+ ctx.log.info('A tool in a delegated turn needs a person; the turn fails', {
35
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
36
+ [NAMZU.ITERATION]: iterationNum,
37
+ [GENAI.TOOL_NAME]: requested.toolName,
38
+ })
39
+ throw new NamzuError({
40
+ code: 'tool_error',
41
+ message: `${requested.toolName} needs a person: ${handoff.reason}`,
42
+ details: { toolName: requested.toolName, handoff },
43
+ retryable: false,
44
+ })
45
+ }
46
+
47
+ const checkpoint = await ctx.checkpointMgr.create(ctx.recorder, iterationNum)
48
+ await ctx.emitEvent({
49
+ type: 'checkpoint_created',
50
+ turnId: ctx.recorder.turnId,
51
+ checkpointId: checkpoint.id,
52
+ iteration: iterationNum,
53
+ })
54
+ yield* ctx.drainPending()
55
+
56
+ await ctx.emitEvent({
57
+ type: 'turn_paused',
58
+ budget: ctx.recorder.budget?.summary(),
59
+ turnId: ctx.recorder.turnId,
60
+ checkpointId: checkpoint.id,
61
+ reason: handoff.reason,
62
+ handoff,
63
+ })
64
+ yield* ctx.drainPending()
65
+ ctx.recorder.setStopReason('paused')
66
+ ctx.log.info('Turn paused for a person', {
67
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
68
+ [NAMZU.ITERATION]: iterationNum,
69
+ [GENAI.TOOL_NAME]: requested.toolName,
70
+ 'namzu.checkpoint.id': checkpoint.id,
71
+ 'namzu.runtime.reason': handoff.reason,
72
+ })
73
+ return 'stop'
74
+ }
@@ -6,3 +6,4 @@ export type { ToolReviewOutcome } from './tool-review.js'
6
6
  export { runIterationCheckpoint } from './checkpoint.js'
7
7
  export { runCompactionCheck } from './compaction.js'
8
8
  export { runAdvisoryPhase } from './advisory.js'
9
+ export { runHandoffPause } from './handoff.js'