@namzu/sdk 45.1.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 (118) hide show
  1. package/CHANGELOG.md +111 -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 +25 -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 +19 -0
  8. package/dist/authorization/rules.js.map +1 -1
  9. package/dist/authorization/shell-lexer.d.ts +24 -0
  10. package/dist/authorization/shell-lexer.d.ts.map +1 -1
  11. package/dist/authorization/shell-lexer.js +69 -51
  12. package/dist/authorization/shell-lexer.js.map +1 -1
  13. package/dist/pricing/catalogue.generated.d.ts.map +1 -1
  14. package/dist/pricing/catalogue.generated.js +28 -4
  15. package/dist/pricing/catalogue.generated.js.map +1 -1
  16. package/dist/public-runtime.d.ts +3 -3
  17. package/dist/public-runtime.d.ts.map +1 -1
  18. package/dist/public-runtime.js +4 -2
  19. package/dist/public-runtime.js.map +1 -1
  20. package/dist/public-tools.d.ts +3 -2
  21. package/dist/public-tools.d.ts.map +1 -1
  22. package/dist/public-tools.js +5 -2
  23. package/dist/public-tools.js.map +1 -1
  24. package/dist/public-types.d.ts +3 -3
  25. package/dist/public-types.d.ts.map +1 -1
  26. package/dist/registry/tool/callable.d.ts +22 -0
  27. package/dist/registry/tool/callable.d.ts.map +1 -0
  28. package/dist/registry/tool/callable.js +29 -0
  29. package/dist/registry/tool/callable.js.map +1 -0
  30. package/dist/registry/tool/execute.d.ts.map +1 -1
  31. package/dist/registry/tool/execute.js +8 -1
  32. package/dist/registry/tool/execute.js.map +1 -1
  33. package/dist/runtime/query/executor/tool-call-admission.d.ts +37 -11
  34. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  35. package/dist/runtime/query/executor/tool-call-admission.js +38 -12
  36. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  37. package/dist/runtime/query/executor.d.ts +4 -2
  38. package/dist/runtime/query/executor.d.ts.map +1 -1
  39. package/dist/runtime/query/executor.js +18 -5
  40. package/dist/runtime/query/executor.js.map +1 -1
  41. package/dist/runtime/query/review-policy.d.ts +43 -0
  42. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  43. package/dist/runtime/query/review-policy.js +59 -16
  44. package/dist/runtime/query/review-policy.js.map +1 -1
  45. package/dist/skills/registry.d.ts +6 -0
  46. package/dist/skills/registry.d.ts.map +1 -1
  47. package/dist/skills/registry.js +1 -0
  48. package/dist/skills/registry.js.map +1 -1
  49. package/dist/tools/builtins/computer-use-coordinates.d.ts +65 -0
  50. package/dist/tools/builtins/computer-use-coordinates.d.ts.map +1 -0
  51. package/dist/tools/builtins/computer-use-coordinates.js +123 -0
  52. package/dist/tools/builtins/computer-use-coordinates.js.map +1 -0
  53. package/dist/tools/builtins/computer-use-image.d.ts +77 -0
  54. package/dist/tools/builtins/computer-use-image.d.ts.map +1 -0
  55. package/dist/tools/builtins/computer-use-image.js +223 -0
  56. package/dist/tools/builtins/computer-use-image.js.map +1 -0
  57. package/dist/tools/builtins/computer-use.d.ts +519 -14
  58. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  59. package/dist/tools/builtins/computer-use.js +1188 -183
  60. package/dist/tools/builtins/computer-use.js.map +1 -1
  61. package/dist/tools/builtins/index.d.ts +2 -1
  62. package/dist/tools/builtins/index.d.ts.map +1 -1
  63. package/dist/tools/builtins/index.js +1 -1
  64. package/dist/tools/builtins/index.js.map +1 -1
  65. package/dist/tools/builtins/skill.d.ts +74 -0
  66. package/dist/tools/builtins/skill.d.ts.map +1 -1
  67. package/dist/tools/builtins/skill.js +214 -174
  68. package/dist/tools/builtins/skill.js.map +1 -1
  69. package/dist/tools/defineTool.d.ts +2 -0
  70. package/dist/tools/defineTool.d.ts.map +1 -1
  71. package/dist/tools/defineTool.js +7 -0
  72. package/dist/tools/defineTool.js.map +1 -1
  73. package/dist/tools/schedules/present.d.ts.map +1 -1
  74. package/dist/tools/schedules/present.js +2 -0
  75. package/dist/tools/schedules/present.js.map +1 -1
  76. package/dist/tools/schedules/schedule-tool.d.ts +6 -5
  77. package/dist/tools/schedules/schedule-tool.d.ts.map +1 -1
  78. package/dist/tools/schedules/schedule-tool.js +121 -30
  79. package/dist/tools/schedules/schedule-tool.js.map +1 -1
  80. package/dist/tools/schedules/types.d.ts +69 -1
  81. package/dist/tools/schedules/types.d.ts.map +1 -1
  82. package/dist/types/authorization/index.d.ts +21 -0
  83. package/dist/types/authorization/index.d.ts.map +1 -1
  84. package/dist/types/authorization/index.js +5 -0
  85. package/dist/types/authorization/index.js.map +1 -1
  86. package/dist/types/computer-use/index.d.ts +176 -0
  87. package/dist/types/computer-use/index.d.ts.map +1 -1
  88. package/dist/types/computer-use/index.js.map +1 -1
  89. package/dist/types/tool/index.d.ts +19 -0
  90. package/dist/types/tool/index.d.ts.map +1 -1
  91. package/dist/types/tool/index.js.map +1 -1
  92. package/package.json +3 -1
  93. package/src/authorization/gate.ts +25 -4
  94. package/src/authorization/rules.ts +18 -0
  95. package/src/authorization/shell-lexer.ts +73 -45
  96. package/src/pricing/catalogue.generated.ts +28 -4
  97. package/src/pricing/rates.source.json +27 -6
  98. package/src/public-runtime.ts +16 -2
  99. package/src/public-tools.ts +19 -1
  100. package/src/public-types.ts +5 -0
  101. package/src/registry/tool/callable.ts +37 -0
  102. package/src/registry/tool/execute.ts +8 -1
  103. package/src/runtime/query/executor/tool-call-admission.ts +60 -16
  104. package/src/runtime/query/executor.ts +19 -4
  105. package/src/runtime/query/review-policy.ts +103 -15
  106. package/src/skills/registry.ts +8 -0
  107. package/src/tools/builtins/computer-use-coordinates.ts +144 -0
  108. package/src/tools/builtins/computer-use-image.ts +278 -0
  109. package/src/tools/builtins/computer-use.ts +1485 -191
  110. package/src/tools/builtins/index.ts +7 -1
  111. package/src/tools/builtins/skill.ts +304 -174
  112. package/src/tools/defineTool.ts +10 -0
  113. package/src/tools/schedules/present.ts +2 -0
  114. package/src/tools/schedules/schedule-tool.ts +139 -33
  115. package/src/tools/schedules/types.ts +69 -1
  116. package/src/types/authorization/index.ts +21 -0
  117. package/src/types/computer-use/index.ts +202 -0
  118. package/src/types/tool/index.ts +19 -0
@@ -7,7 +7,9 @@ import { scanSchedulePrompt } from './prompt-scan.js'
7
7
  import type {
8
8
  ScheduleBrowserGrant,
9
9
  ScheduleBrowserSiteLevel,
10
+ ScheduleJobChanges,
10
11
  ScheduleJobDraft,
12
+ ScheduleJobUpdateProposal,
11
13
  ScheduleToolHost,
12
14
  } from './types.js'
13
15
 
@@ -15,7 +17,8 @@ export const SCHEDULE_TOOL_NAME = 'schedule'
15
17
 
16
18
  /**
17
19
  * A person reads the whole proposal — the prompt, the rules, the schedule,
18
- * the credential source — before answering `create`, `resume` or `delete`.
20
+ * the credential source — before answering `create`, `update`, `resume` or
21
+ * `delete`.
19
22
  * The executor's `DEFAULT_TOOL_TIMEOUT_MS` (two minutes) is sized for a tool
20
23
  * call, not a person on the other end of a screen, and would abandon the
21
24
  * call out from under them mid-read. `save_skill`
@@ -30,18 +33,25 @@ const BROWSER_PROFILE = /^[a-z0-9][a-z0-9-]{0,62}$/
30
33
 
31
34
  const inputSchema = z.object({
32
35
  action: z
33
- .enum(['create', 'list', 'pause', 'resume', 'delete'])
34
- .describe('What to do. create, resume and delete ask the operator first.'),
36
+ .enum(['create', 'list', 'update', 'pause', 'resume', 'delete'])
37
+ .describe(
38
+ 'What to do. create, update, resume and delete ask the operator first. To change a job, update it: deleting and creating it again loses its history.',
39
+ ),
35
40
  name: z
36
41
  .string()
37
42
  .regex(/^[a-z0-9][a-z0-9-]{0,62}$/)
38
43
  .optional()
39
44
  .describe('create: job name, lowercase letters, digits and dashes'),
40
- prompt: z.string().min(1).max(20_000).optional().describe('create: what the run is asked to do'),
45
+ prompt: z
46
+ .string()
47
+ .min(1)
48
+ .max(20_000)
49
+ .optional()
50
+ .describe('create, update: what the run is asked to do'),
41
51
  when: z
42
52
  .string()
43
53
  .optional()
44
- .describe('create: "every 30m", "0 9 * * 1-5" (cron), "at 2026-09-24 09:00", "in 2h"'),
54
+ .describe('create, update: "every 30m", "0 9 * * 1-5" (cron), "at 2026-09-24 09:00", "in 2h"'),
45
55
  folder: z
46
56
  .string()
47
57
  .optional()
@@ -90,7 +100,9 @@ const inputSchema = z.object({
90
100
  .describe('Browser access; omit for none'),
91
101
  })
92
102
  .optional()
93
- .describe('create: REQUIRED explicit permission set; there is no default'),
103
+ .describe(
104
+ 'create: REQUIRED explicit permission set; there is no default. update: the whole new set, only when the permissions change',
105
+ ),
94
106
  budget: z
95
107
  .object({
96
108
  maxIterations: z
@@ -118,7 +130,7 @@ const inputSchema = z.object({
118
130
  })
119
131
  .optional()
120
132
  .describe('Limits of ONE run; omit to use the defaults'),
121
- job: z.string().optional().describe('pause/resume/delete: job name'),
133
+ job: z.string().optional().describe('update/pause/resume/delete: job name'),
122
134
  allFolders: z.boolean().optional().describe('list: include jobs of other folders (names only)'),
123
135
  })
124
136
 
@@ -208,6 +220,27 @@ function browserGrant(
208
220
  }
209
221
  }
210
222
 
223
+ /** A proposed permission set as a draft carries it, or why it is refused. */
224
+ function checkPermissions(
225
+ host: ScheduleToolHost,
226
+ proposed: NonNullable<Input['permissions']>,
227
+ ): { ok: true; permissions: ScheduleJobDraft['permissions'] } | { ok: false; error: string } {
228
+ if (!proposed.preset && !proposed.rules && !proposed.browser) {
229
+ return {
230
+ ok: false,
231
+ error:
232
+ 'permissions needs a preset, rules or a browser grant; an empty permission set is not a choice.',
233
+ }
234
+ }
235
+ if (!proposed.browser) return { ok: true, permissions: proposed }
236
+ const grant = browserGrant(host, proposed.browser)
237
+ if (!grant.ok) return grant
238
+ return { ok: true, permissions: { ...proposed, browser: grant.grant } }
239
+ }
240
+
241
+ const NETWORK_WITH_HOST_SHELL =
242
+ 'A scheduled job proposed here cannot combine web or browser access with a shell on the host. Deny bash, use execution "sandbox", or ask the operator to create it with `namzu schedule add`.'
243
+
211
244
  async function create(
212
245
  host: ScheduleToolHost,
213
246
  input: Input,
@@ -221,18 +254,9 @@ async function create(
221
254
  `create needs ${missing.join(', ')}. permissions is required: propose an explicit set (a preset and/or rules, plus unmatched).`,
222
255
  )
223
256
  }
224
- const proposed = input.permissions as NonNullable<Input['permissions']>
225
- if (!proposed.preset && !proposed.rules && !proposed.browser) {
226
- return refuse(
227
- 'permissions needs a preset, rules or a browser grant; an empty permission set is not a choice.',
228
- )
229
- }
230
- let permissions: ScheduleJobDraft['permissions'] = proposed
231
- if (proposed.browser) {
232
- const grant = browserGrant(host, proposed.browser)
233
- if (!grant.ok) return refuse(grant.error)
234
- permissions = { ...proposed, browser: grant.grant }
235
- }
257
+ const checked = checkPermissions(host, input.permissions as NonNullable<Input['permissions']>)
258
+ if (!checked.ok) return refuse(checked.error)
259
+ const permissions = checked.permissions
236
260
  const draft: ScheduleJobDraft = {
237
261
  name: input.name as string,
238
262
  prompt: input.prompt as string,
@@ -242,11 +266,7 @@ async function create(
242
266
  permissions,
243
267
  ...(input.budget ? { budget: input.budget } : {}),
244
268
  }
245
- if (networkWithHostShell(draft)) {
246
- return refuse(
247
- 'A scheduled job proposed here cannot combine web or browser access with a shell on the host. Deny bash, use execution "sandbox", or ask the operator to create it with `namzu schedule add`.',
248
- )
249
- }
269
+ if (networkWithHostShell(draft)) return refuse(NETWORK_WITH_HOST_SHELL)
250
270
  let preview: Awaited<ReturnType<ScheduleToolHost['preview']>>
251
271
  try {
252
272
  preview = await host.preview(draft)
@@ -295,11 +315,94 @@ async function list(host: ScheduleToolHost, input: Input): Promise<ToolResult> {
295
315
  if (jobs.length === 0) return { success: true, output: 'No scheduled jobs.', data: { jobs: [] } }
296
316
  const lines = jobs.map(
297
317
  (j) =>
298
- `${j.name} · ${j.state} · ${j.schedule}${j.nextFireAt ? ` · next ${j.nextFireAt}` : ''}${j.lastStatus ? ` · last ${j.lastStatus}` : ''} · ${j.folder}`,
318
+ `${j.name} · ${j.state} · ${j.schedule}${j.nextFireAt ? ` · next ${j.nextFireAt}` : ''}${j.lastStatus ? ` · last ${j.lastStatus}` : ''} · ${j.folder}${j.inSessionFolder ? ' (this folder)' : ''}`,
299
319
  )
300
320
  return { success: true, output: lines.join('\n'), data: { jobs } }
301
321
  }
302
322
 
323
+ /** The fields `update` may change, as the model set them. */
324
+ const CHANGEABLE = ['prompt', 'when', 'folder', 'tz', 'permissions', 'budget'] as const
325
+
326
+ async function update(
327
+ host: ScheduleToolHost,
328
+ input: Input,
329
+ signal: AbortSignal | undefined,
330
+ ): Promise<ToolResult> {
331
+ if (!host.previewUpdate || !host.confirmUpdate || !host.update) {
332
+ return refuse(
333
+ 'This host cannot change a scheduled job. Ask the operator to change it themselves; do not delete and create it again, which loses its history.',
334
+ )
335
+ }
336
+ if (!input.job) return refuse("update needs job (the job's name) and the fields to change.")
337
+ if (input.name !== undefined) {
338
+ return refuse(
339
+ 'update cannot rename a job: name the job to change with job, and leave name out.',
340
+ )
341
+ }
342
+ const given = CHANGEABLE.filter((k) => input[k] !== undefined)
343
+ if (given.length === 0) {
344
+ return refuse(`update needs at least one of ${CHANGEABLE.join(', ')} to change.`)
345
+ }
346
+ let permissions: ScheduleJobDraft['permissions'] | undefined
347
+ if (input.permissions) {
348
+ const checked = checkPermissions(host, input.permissions)
349
+ if (!checked.ok) return refuse(checked.error)
350
+ permissions = checked.permissions
351
+ if (networkWithHostShell({ name: '', prompt: '', when: '', permissions }))
352
+ return refuse(NETWORK_WITH_HOST_SHELL)
353
+ }
354
+ const changes: ScheduleJobChanges = {
355
+ ...(input.prompt !== undefined ? { prompt: input.prompt } : {}),
356
+ ...(input.when !== undefined ? { when: input.when } : {}),
357
+ ...(input.folder !== undefined ? { folder: input.folder } : {}),
358
+ ...(input.tz !== undefined ? { tz: input.tz } : {}),
359
+ ...(permissions ? { permissions } : {}),
360
+ ...(input.budget ? { budget: input.budget } : {}),
361
+ }
362
+ let proposal: ScheduleJobUpdateProposal
363
+ try {
364
+ proposal = await host.previewUpdate(input.job, changes)
365
+ } catch (error) {
366
+ return refuse(error instanceof Error ? error.message : String(error))
367
+ }
368
+ let confirmed = false
369
+ try {
370
+ confirmed = await host.confirmUpdate(
371
+ {
372
+ ...proposal,
373
+ promptFindings: scanSchedulePrompt(proposal.preview.prompt),
374
+ proposedBy: 'model',
375
+ },
376
+ signal,
377
+ )
378
+ } catch {
379
+ confirmed = false
380
+ }
381
+ // As for `create`: never save on an answer that came after the
382
+ // operator was no longer being asked.
383
+ if (signal?.aborted) confirmed = false
384
+ if (!confirmed) {
385
+ return {
386
+ ...refuse(
387
+ `The operator did not confirm the change; "${proposal.preview.name}" was not changed.`,
388
+ ),
389
+ data: { cancelled: true },
390
+ }
391
+ }
392
+ let saved: Awaited<ReturnType<NonNullable<ScheduleToolHost['update']>>>
393
+ try {
394
+ saved = await host.update(proposal.preview)
395
+ } catch (error) {
396
+ return refuse(error instanceof Error ? error.message : String(error))
397
+ }
398
+ const said = `Job "${saved.name}" was updated in place and keeps its history. It runs ${proposal.preview.schedule}.`
399
+ return {
400
+ success: true,
401
+ output: saved.note ? `${said} ${saved.note}` : said,
402
+ data: { name: saved.name, updated: true, changes: proposal.changes },
403
+ }
404
+ }
405
+
303
406
  async function lifecycle(
304
407
  host: ScheduleToolHost,
305
408
  input: Input,
@@ -331,12 +434,13 @@ async function lifecycle(
331
434
  }
332
435
 
333
436
  /**
334
- * The `schedule` tool: create, list, pause, resume and delete the operator's
335
- * scheduled jobs from a conversation.
437
+ * The `schedule` tool: create, list, update, pause, resume and delete the
438
+ * operator's scheduled jobs from a conversation.
336
439
  *
337
- * Creation, resuming and deleting are confirmed by a person through the host
338
- * (`ScheduleToolHost.confirm`), on a screen the host draws from its own
339
- * computation. The model cannot propose that uncovered calls run without
440
+ * Creation, updating, resuming and deleting are confirmed by a person
441
+ * through the host (`ScheduleToolHost.confirm`, `confirmUpdate`), on a
442
+ * screen the host draws from its own computation. An update changes the
443
+ * job in place, so it keeps its id and its history. The model cannot propose that uncovered calls run without
340
444
  * asking, nor web access beside a host shell. Register it only where a person
341
445
  * is present to confirm: never in a headless run, a scheduled run or a
342
446
  * sub-agent.
@@ -346,7 +450,7 @@ export function buildScheduleTools(host: ScheduleToolHost): ToolDefinition[] {
346
450
  defineTool({
347
451
  name: SCHEDULE_TOOL_NAME,
348
452
  description:
349
- "Manage the operator's scheduled jobs: prompts that run later in a folder, with nobody watching, under an explicit permission set. Use it only when the user asks for something to happen on a schedule. create, resume and delete are confirmed by the operator; pause is not. A job needs name, prompt, when and permissions (unmatched: park or deny, plus a preset, rules or a browser grant). Leave every other field (folder, tz, execution, budget, headed) unset unless the user asked for it: the defaults are the operator's, and the confirmation marks each value you chose. Scheduled runs cannot ask questions.",
453
+ "Manage the operator's scheduled jobs: prompts that run later in a folder, with nobody watching, under an explicit permission set. Use it only when the user asks for something to happen on a schedule. create, update, resume and delete are confirmed by the operator; pause is not. A job needs name, prompt, when and permissions (unmatched: park or deny, plus a preset, rules or a browser grant). To change a job, update it with job and only the fields that change; do not delete and recreate it. Leave every other field (folder, tz, execution, budget, headed) unset unless the user asked for it: the defaults are the operator's, and the confirmation marks each value you chose. Scheduled runs cannot ask questions.",
350
454
  inputSchema,
351
455
  category: 'custom',
352
456
  permissions: [],
@@ -358,8 +462,8 @@ export function buildScheduleTools(host: ScheduleToolHost): ToolDefinition[] {
358
462
  concurrencySafe: false,
359
463
  presentCall: presentScheduleCall,
360
464
  presentResult: presentScheduleResult,
361
- // A person, not a tool, answers `create`/`resume`/`delete`; see
362
- // `OPERATOR_CONFIRM_TIMEOUT_MS`.
465
+ // A person, not a tool, answers `create`/`update`/`resume`/`delete`;
466
+ // see `OPERATOR_CONFIRM_TIMEOUT_MS`.
363
467
  timeoutMs: OPERATOR_CONFIRM_TIMEOUT_MS,
364
468
  async execute(input, context: ToolContext) {
365
469
  switch (input.action) {
@@ -367,6 +471,8 @@ export function buildScheduleTools(host: ScheduleToolHost): ToolDefinition[] {
367
471
  return create(host, input, context.abortSignal)
368
472
  case 'list':
369
473
  return list(host, input)
474
+ case 'update':
475
+ return update(host, input, context.abortSignal)
370
476
  default:
371
477
  return lifecycle(host, input, input.action, context.abortSignal)
372
478
  }
@@ -114,6 +114,48 @@ export interface ScheduleJobSummary {
114
114
  readonly lastStatus?: string
115
115
  /** Present only for jobs in the session's own folder. */
116
116
  readonly prompt?: string
117
+ /**
118
+ * True for a job whose folder is the session's own. A host that lists
119
+ * other folders' jobs sets it so the model can tell them apart; the tool
120
+ * marks such a job `(this folder)`.
121
+ */
122
+ readonly inSessionFolder?: boolean
123
+ }
124
+
125
+ /**
126
+ * What the model proposes to change in an existing job. Every field left
127
+ * out keeps the job's current value. `permissions`, when given, is the whole
128
+ * new set, with the same limits as a new job's; the host keeps the job's
129
+ * `execution` when it is not given, and its additional directories.
130
+ */
131
+ export interface ScheduleJobChanges {
132
+ readonly prompt?: string
133
+ readonly when?: string
134
+ readonly folder?: string
135
+ readonly tz?: string
136
+ readonly permissions?: ScheduleJobDraft['permissions']
137
+ readonly budget?: ScheduleJobDraft['budget']
138
+ }
139
+
140
+ /** A proposed change to a job, as the host computed it. */
141
+ export interface ScheduleJobUpdateProposal {
142
+ /** The job as it would be once changed. Every field is the host's own computation. */
143
+ readonly preview: ScheduleJobPreview
144
+ /**
145
+ * What differs from the job as it stands (and from edits saved since it
146
+ * was last confirmed), one line each: `- ` what goes, `+ ` what comes.
147
+ */
148
+ readonly changes: readonly string[]
149
+ /** The change touches what a run may do: its rules, `unmatched`, where it runs, its browser grant. */
150
+ readonly permissionsChange: boolean
151
+ }
152
+
153
+ /** What the person confirming a change is shown. */
154
+ export interface ScheduleUpdateRequest extends ScheduleJobUpdateProposal {
155
+ /** The prompt tripwire's findings over `preview.prompt`. */
156
+ readonly promptFindings: readonly string[]
157
+ /** Always `model` from this tool. */
158
+ readonly proposedBy: 'model'
117
159
  }
118
160
 
119
161
  /** What the person answered to a proposed job. */
@@ -157,7 +199,14 @@ export interface ScheduleToolHost {
157
199
  preview: ScheduleJobPreview,
158
200
  options: { readonly paused: boolean },
159
201
  ): Promise<{ readonly name: string; readonly note?: string }>
160
- /** Jobs, the session folder's in full, other folders' without their prompts. */
202
+ /**
203
+ * Jobs, the session folder's in full, other folders' without their
204
+ * prompts. `allFolders: false` lets a host list only the session
205
+ * folder's; a host may list every job regardless, marking the session
206
+ * folder's with {@link ScheduleJobSummary.inSessionFolder}. The tool says
207
+ * "No scheduled jobs." when this returns none, so a host that filters
208
+ * says so only for its folder.
209
+ */
161
210
  list(options: { readonly allFolders: boolean }): Promise<readonly ScheduleJobSummary[]>
162
211
  /** A job by name or id prefix, or undefined. */
163
212
  find(job: string): Promise<ScheduleJobSummary | undefined>
@@ -170,6 +219,25 @@ export interface ScheduleToolHost {
170
219
  pause(job: string): Promise<void>
171
220
  resume(job: string): Promise<void>
172
221
  delete(job: string): Promise<void>
222
+ /**
223
+ * Validate a change to `job` (a name or id prefix) and compute what the
224
+ * person will be shown. Throws with a message on a refusal. The tool
225
+ * offers `update` only when a host has all three of `previewUpdate`,
226
+ * `confirmUpdate` and `update`.
227
+ */
228
+ previewUpdate?(job: string, changes: ScheduleJobChanges): Promise<ScheduleJobUpdateProposal>
229
+ /**
230
+ * Ask the person whether to save the change. Anything but `true` — a
231
+ * thrown error, a closed screen — changes nothing. See `confirm` on
232
+ * `signal`.
233
+ */
234
+ confirmUpdate?(request: ScheduleUpdateRequest, signal?: AbortSignal): Promise<boolean>
235
+ /**
236
+ * Save the change the person confirmed, to the same job: its id and its
237
+ * history are kept, and the confirmation is the person's. `note` as for
238
+ * `create`.
239
+ */
240
+ update?(preview: ScheduleJobPreview): Promise<{ readonly name: string; readonly note?: string }>
173
241
  }
174
242
 
175
243
  /** A prompt the session re-sends to itself on an interval. */
@@ -115,6 +115,19 @@ export type AuthorizationRule =
115
115
  */
116
116
  description: string
117
117
  decide: AuthorizationPredicate
118
+ /**
119
+ * The reason for THIS call, when `decide` returned a decision: what
120
+ * in the input matched, and where. The gate reports it instead of
121
+ * {@link description}. Absent, returning `null` or throwing, the
122
+ * gate reports `description`.
123
+ *
124
+ * A rule that refuses on several grounds and reports one fixed
125
+ * sentence leaves the model, and the person reading the refusal,
126
+ * to guess which ground it was. A scheduled run's refusal listed
127
+ * everything the rule protects when one word of the command had
128
+ * matched.
129
+ */
130
+ describe?: (call: AuthorizationPredicateCall) => string | null
118
131
  }
119
132
 
120
133
  /** The call an `AuthorizationRule` of type `predicate` is asked about. */
@@ -183,6 +196,14 @@ const PredicateSchema = z.object({
183
196
  decide: z.custom<AuthorizationPredicate>((value) => typeof value === 'function', {
184
197
  message: 'decide must be a function',
185
198
  }),
199
+ // Named here so the gate's own parse keeps it: zod strips what a schema
200
+ // does not name.
201
+ describe: z
202
+ .custom<(call: AuthorizationPredicateCall) => string | null>(
203
+ (value) => typeof value === 'function',
204
+ { message: 'describe must be a function' },
205
+ )
206
+ .optional(),
186
207
  })
187
208
 
188
209
  export const AuthorizationRuleSchema = z.discriminatedUnion('type', [
@@ -44,10 +44,41 @@ export interface ComputerUseCapabilities {
44
44
  * model reads the reason once and does not try again.
45
45
  */
46
46
  readonly unavailableReason?: string
47
+ /**
48
+ * The host implements {@link ComputerUseHost.listWindows} and
49
+ * {@link ComputerUseHost.focusWindow}. Absent or false, the tool does not
50
+ * offer `list_windows` or `focus_window`, even when the methods exist.
51
+ */
52
+ readonly windows?: boolean
53
+ /**
54
+ * The host implements {@link ComputerUseHost.captureRegion}. Absent or
55
+ * false, `zoom` still works: the tool crops a full capture instead, which
56
+ * costs one whole-display capture per zoom.
57
+ */
58
+ readonly regionCapture?: boolean
59
+ /**
60
+ * The host implements {@link ComputerUseHost.uiSnapshot} and
61
+ * {@link ComputerUseHost.uiAct}: an accessibility tree (Windows UI
62
+ * Automation, macOS AX, AT-SPI, or a driver that wraps one) whose
63
+ * elements can be acted on by reference instead of by pixel.
64
+ *
65
+ * @experimental The UI-tree surface is reserved for the host packages that
66
+ * implement it; its shape may still change in a minor release.
67
+ */
68
+ readonly uiTree?: boolean
47
69
  }
48
70
 
49
71
  // ---------------------------------------------------------------------------
50
72
  // Geometry + screenshot payload
73
+ //
74
+ // Units, once for the whole file: every coordinate and size a host accepts or
75
+ // returns is in PHYSICAL pixels — the pixels of the captured bitmap, not
76
+ // logical points or DPI-scaled units. A 3440x1440 monitor at 150 % scaling is
77
+ // 3440x1440 here. A point a host is asked to act on is relative to the
78
+ // top-left of the display it last captured (the primary display until a host
79
+ // offers display selection); the host adds that display's origin itself.
80
+ // Window and UI-element bounds are the exception and say so: they are in
81
+ // virtual-desktop physical pixels, because a window can span displays.
51
82
  // ---------------------------------------------------------------------------
52
83
 
53
84
  export interface DisplayGeometry {
@@ -56,11 +87,144 @@ export interface DisplayGeometry {
56
87
  readonly scaleFactor: number
57
88
  }
58
89
 
90
+ /**
91
+ * The display a capture shows.
92
+ *
93
+ * `x`/`y` place it in the virtual desktop (a monitor left of the primary has
94
+ * a negative `x`); `width`/`height` are its physical size. `scaleFactor` is
95
+ * physical pixels per logical pixel — 1 at 96 DPI on Windows, 1.5 at 150 %,
96
+ * 2 on a Retina panel — reported for the host UI and for diagnosis; the tool
97
+ * never multiplies by it, because every coordinate crossing this interface
98
+ * is already physical.
99
+ */
100
+ export interface DisplayInfo {
101
+ /** Stable for the host's lifetime; what a host would accept to select this display. */
102
+ readonly id: string
103
+ readonly x: number
104
+ readonly y: number
105
+ readonly width: number
106
+ readonly height: number
107
+ readonly scaleFactor: number
108
+ /** True for the display the operating system calls primary. */
109
+ readonly primary?: boolean
110
+ }
111
+
59
112
  export interface ScreenshotResult {
60
113
  readonly data: Buffer
61
114
  readonly mimeType: 'image/png'
115
+ /** Physical pixel width of `data`. */
62
116
  readonly width: number
117
+ /** Physical pixel height of `data`. */
63
118
  readonly height: number
119
+ /**
120
+ * The display this capture shows. A host should always set it; one written
121
+ * before it existed does not, and the tool then assumes a single display at
122
+ * the origin whose size is the capture's own, with a scale factor of 1.
123
+ */
124
+ readonly display?: DisplayInfo
125
+ }
126
+
127
+ /** A rectangle in physical pixels. What its origin is relative to depends on where it appears. */
128
+ export interface Rect {
129
+ readonly x: number
130
+ readonly y: number
131
+ readonly width: number
132
+ readonly height: number
133
+ }
134
+
135
+ /** One top-level window, as {@link ComputerUseHost.listWindows} reports it. */
136
+ export interface WindowInfo {
137
+ /** Opaque and host-defined (an HWND in hex, a CGWindowID, an X11 window id); valid for {@link ComputerUseHost.focusWindow}. */
138
+ readonly id: string
139
+ readonly title: string
140
+ /** Application or process name, e.g. `msedge`, `Teams`, `Code`. */
141
+ readonly app: string
142
+ readonly pid: number
143
+ /** Virtual-desktop physical pixels — not display-relative. */
144
+ readonly bounds: Rect
145
+ readonly focused: boolean
146
+ readonly minimized: boolean
147
+ }
148
+
149
+ /**
150
+ * What {@link ComputerUseHost.focusWindow} achieved. Bringing a window to
151
+ * the front is a request the operating system can refuse (Windows'
152
+ * foreground lock is the common case), so a host reports the window that is
153
+ * actually in front afterwards rather than assuming its request held.
154
+ */
155
+ export interface FocusWindowResult {
156
+ /** True only when `focusedId` is the requested window. */
157
+ readonly ok: boolean
158
+ /** The window in front after the attempt, or null when none could be read. */
159
+ readonly focusedId: string | null
160
+ }
161
+
162
+ /**
163
+ * An action an accessibility element can take by reference.
164
+ *
165
+ * @experimental See {@link ComputerUseCapabilities.uiTree}.
166
+ */
167
+ export type UiElementAction =
168
+ | 'invoke'
169
+ | 'focus'
170
+ | 'set_value'
171
+ | 'toggle'
172
+ | 'select'
173
+ | 'expand'
174
+ | 'collapse'
175
+ | 'scroll_into_view'
176
+
177
+ /**
178
+ * One accessibility element.
179
+ *
180
+ * @experimental See {@link ComputerUseCapabilities.uiTree}.
181
+ */
182
+ export interface UiElement {
183
+ /**
184
+ * Opaque reference for {@link ComputerUseHost.uiAct}, valid until the
185
+ * host's next snapshot. Empty for an element the host cannot act on (a
186
+ * label, a group), which is in the tree for what it says.
187
+ */
188
+ readonly ref: string
189
+ /** Platform role, e.g. `Button`, `Edit`, `ListItem` (UIA ControlType) or `AXButton`. */
190
+ readonly role: string
191
+ readonly name: string
192
+ readonly value?: string
193
+ /** A platform automation id, when the element has one. */
194
+ readonly automationId?: string
195
+ /** Virtual-desktop physical pixels, when the element is on screen. */
196
+ readonly bounds?: Rect
197
+ /** Element states such as `focused`, `disabled`, `selected`, `checked`, `expanded`. */
198
+ readonly states?: readonly string[]
199
+ readonly actions?: readonly UiElementAction[]
200
+ readonly children?: readonly UiElement[]
201
+ }
202
+
203
+ /**
204
+ * An accessibility tree for one window.
205
+ *
206
+ * @experimental See {@link ComputerUseCapabilities.uiTree}.
207
+ */
208
+ export interface UiSnapshot {
209
+ readonly windowId?: string
210
+ /** The window's title, as its application sets it. */
211
+ readonly title?: string
212
+ /** The owning application or process name, as in {@link WindowInfo.app}. */
213
+ readonly app?: string
214
+ readonly root: UiElement
215
+ /** True when the host stopped walking the tree before it ended (a size or time bound). */
216
+ readonly truncated?: boolean
217
+ }
218
+
219
+ /**
220
+ * What {@link ComputerUseHost.uiAct} did.
221
+ *
222
+ * @experimental See {@link ComputerUseCapabilities.uiTree}.
223
+ */
224
+ export interface UiActResult {
225
+ readonly ok: boolean
226
+ /** Why it did not, in words the model can act on (a stale ref, a disabled element). */
227
+ readonly detail?: string
64
228
  }
65
229
 
66
230
  export interface Point {
@@ -152,8 +316,46 @@ export interface ComputerUseHost {
152
316
  readonly capabilities: ComputerUseCapabilities
153
317
 
154
318
  getDisplayGeometry(): Promise<DisplayGeometry>
319
+ /**
320
+ * Points in `action` are physical pixels relative to the display of the
321
+ * most recent capture; a `screenshot` result carries that display in
322
+ * {@link ScreenshotResult.display}.
323
+ */
155
324
  execute(action: ComputerUseAction): Promise<ComputerUseResult>
156
325
 
326
+ /**
327
+ * The visible, titled top-level windows, front to back where the platform
328
+ * knows the order. Offered to the model only with
329
+ * {@link ComputerUseCapabilities.windows}.
330
+ */
331
+ listWindows?(): Promise<readonly WindowInfo[]>
332
+ /**
333
+ * Bring a window to the front, restoring it when minimized. Offered only
334
+ * with {@link ComputerUseCapabilities.windows}.
335
+ */
336
+ focusWindow?(id: string): Promise<FocusWindowResult>
337
+ /**
338
+ * Capture one region of the current display at full physical resolution.
339
+ * `rect` is display-relative physical pixels; the result's `width` and
340
+ * `height` are the region's. Used by `zoom` when
341
+ * {@link ComputerUseCapabilities.regionCapture} is set.
342
+ */
343
+ captureRegion?(rect: Rect): Promise<ScreenshotResult>
344
+ /**
345
+ * The accessibility tree of one window, or of the focused window when
346
+ * `windowId` is omitted. Offered only with {@link ComputerUseCapabilities.uiTree}.
347
+ *
348
+ * @experimental
349
+ */
350
+ uiSnapshot?(windowId?: string): Promise<UiSnapshot>
351
+ /**
352
+ * Act on an element from the latest {@link uiSnapshot}. `value` is the text
353
+ * for `set_value`. Offered only with {@link ComputerUseCapabilities.uiTree}.
354
+ *
355
+ * @experimental
356
+ */
357
+ uiAct?(ref: string, action: UiElementAction, value?: string): Promise<UiActResult>
358
+
157
359
  initialize?(): Promise<void>
158
360
  dispose?(): Promise<void>
159
361
  }
@@ -42,6 +42,12 @@ export interface SkillRegistryRef {
42
42
  registeredName: string
43
43
  description: string
44
44
  location: string
45
+ /**
46
+ * The skill's directory as the host reads it. Optional so a registry
47
+ * written before it existed still satisfies this interface; what the
48
+ * `skill` tool hands a host's `resolveModelDirectory` to map.
49
+ */
50
+ directory?: string
45
51
  allowedTools?: string
46
52
  invocation?: 'model' | 'operator' | 'both'
47
53
  }[]
@@ -50,6 +56,7 @@ export interface SkillRegistryRef {
50
56
  registeredName: string
51
57
  description: string
52
58
  location: string
59
+ directory?: string
53
60
  allowedTools?: string
54
61
  invocation?: 'model' | 'operator' | 'both'
55
62
  }[]
@@ -927,6 +934,18 @@ export interface ToolDefinition<TInput = unknown> extends ToolPresentation<TInpu
927
934
  isReadOnly?(input: TInput): boolean
928
935
  isDestructive?(input: TInput): boolean
929
936
  isConcurrencySafe?(input: TInput): boolean
937
+ /**
938
+ * This call sends what is on the operator's screen to the model provider:
939
+ * a screenshot, the titles of the open windows, a window's accessibility
940
+ * tree.
941
+ *
942
+ * Orthogonal to {@link isReadOnly}: a screenshot changes nothing and is
943
+ * still the one read a person may want to allow before it happens. A
944
+ * review policy given a consent record (`createReviewHandler`'s
945
+ * `screenConsent`) asks once per session before the first such call.
946
+ * Absent means the tool never does.
947
+ */
948
+ capturesScreen?(input: TInput): boolean
930
949
 
931
950
  /**
932
951
  * Opt-in ordering boundary in a direct model tool-call batch. Earlier