@anionex/dsh-computer-use 0.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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +307 -0
  4. package/README.zh.md +307 -0
  5. package/assets/computer-use-fixture.png +0 -0
  6. package/assets/cursor.png +0 -0
  7. package/cordis.patch.yml +13 -0
  8. package/docs/interaction-policy.i18n.yaml +4 -0
  9. package/docs/interaction-policy.md +140 -0
  10. package/docs/interaction-policy.zh.md +140 -0
  11. package/lib/approval-policy.js +12 -0
  12. package/lib/approval-policy.js.map +1 -0
  13. package/lib/artifacts.js +79 -0
  14. package/lib/artifacts.js.map +1 -0
  15. package/lib/backend.js +3 -0
  16. package/lib/backend.js.map +1 -0
  17. package/lib/client.js +349 -0
  18. package/lib/client.js.map +1 -0
  19. package/lib/config.js +120 -0
  20. package/lib/config.js.map +1 -0
  21. package/lib/confirmations.js +99 -0
  22. package/lib/confirmations.js.map +1 -0
  23. package/lib/diff.js +58 -0
  24. package/lib/diff.js.map +1 -0
  25. package/lib/errors.js +22 -0
  26. package/lib/errors.js.map +1 -0
  27. package/lib/exposure.js +231 -0
  28. package/lib/exposure.js.map +1 -0
  29. package/lib/index.js +56 -0
  30. package/lib/index.js.map +1 -0
  31. package/lib/leases.js +259 -0
  32. package/lib/leases.js.map +1 -0
  33. package/lib/providers/macos.js +148 -0
  34. package/lib/providers/macos.js.map +1 -0
  35. package/lib/providers/native-helper.js +356 -0
  36. package/lib/providers/native-helper.js.map +1 -0
  37. package/lib/service.js +480 -0
  38. package/lib/service.js.map +1 -0
  39. package/lib/skill.js +119 -0
  40. package/lib/skill.js.map +1 -0
  41. package/lib/target-resolver.js +180 -0
  42. package/lib/target-resolver.js.map +1 -0
  43. package/lib/tools.js +493 -0
  44. package/lib/tools.js.map +1 -0
  45. package/lib/types/approval-policy.d.ts +13 -0
  46. package/lib/types/approval-policy.d.ts.map +1 -0
  47. package/lib/types/artifacts.d.ts +10 -0
  48. package/lib/types/artifacts.d.ts.map +1 -0
  49. package/lib/types/backend.d.ts +94 -0
  50. package/lib/types/backend.d.ts.map +1 -0
  51. package/lib/types/client/index.d.ts +78 -0
  52. package/lib/types/client/index.d.ts.map +1 -0
  53. package/lib/types/config.d.ts +75 -0
  54. package/lib/types/config.d.ts.map +1 -0
  55. package/lib/types/confirmations.d.ts +23 -0
  56. package/lib/types/confirmations.d.ts.map +1 -0
  57. package/lib/types/diff.d.ts +5 -0
  58. package/lib/types/diff.d.ts.map +1 -0
  59. package/lib/types/errors.d.ts +15 -0
  60. package/lib/types/errors.d.ts.map +1 -0
  61. package/lib/types/exposure.d.ts +30 -0
  62. package/lib/types/exposure.d.ts.map +1 -0
  63. package/lib/types/index.d.ts +21 -0
  64. package/lib/types/index.d.ts.map +1 -0
  65. package/lib/types/leases.d.ts +80 -0
  66. package/lib/types/leases.d.ts.map +1 -0
  67. package/lib/types/providers/macos.d.ts +15 -0
  68. package/lib/types/providers/macos.d.ts.map +1 -0
  69. package/lib/types/providers/native-helper.d.ts +36 -0
  70. package/lib/types/providers/native-helper.d.ts.map +1 -0
  71. package/lib/types/service.d.ts +52 -0
  72. package/lib/types/service.d.ts.map +1 -0
  73. package/lib/types/skill.d.ts +9 -0
  74. package/lib/types/skill.d.ts.map +1 -0
  75. package/lib/types/target-resolver.d.ts +38 -0
  76. package/lib/types/target-resolver.d.ts.map +1 -0
  77. package/lib/types/tools.d.ts +6 -0
  78. package/lib/types/tools.d.ts.map +1 -0
  79. package/lib/types/types.d.ts +246 -0
  80. package/lib/types/types.d.ts.map +1 -0
  81. package/lib/types/web.d.ts +32 -0
  82. package/lib/types/web.d.ts.map +1 -0
  83. package/lib/types.js +9 -0
  84. package/lib/types.js.map +1 -0
  85. package/lib/web.js +163 -0
  86. package/lib/web.js.map +1 -0
  87. package/native/macos/Sources/Fixture/main.swift +366 -0
  88. package/native/macos/Sources/Helper/CursorImage.swift +11 -0
  89. package/native/macos/Sources/Helper/CursorOverlay.swift +488 -0
  90. package/native/macos/Sources/Helper/TargetedPointer.swift +314 -0
  91. package/native/macos/Sources/Helper/main.swift +1140 -0
  92. package/native/macos/Sources/Monitor/main.swift +139 -0
  93. package/native/macos/bin/dsh-computer-use-helper +0 -0
  94. package/native/macos/manifest.json +14 -0
  95. package/package.json +128 -0
  96. package/scripts/build-client.mjs +24 -0
  97. package/scripts/build-native.mjs +158 -0
  98. package/scripts/check-native.mjs +92 -0
  99. package/scripts/model-e2e.mjs +291 -0
  100. package/scripts/session-transcript.mjs +117 -0
  101. package/scripts/validate.mjs +256 -0
  102. package/src/approval-policy.ts +16 -0
  103. package/src/artifacts.ts +93 -0
  104. package/src/backend.ts +103 -0
  105. package/src/client/index.tsx +539 -0
  106. package/src/config.ts +196 -0
  107. package/src/confirmations.ts +128 -0
  108. package/src/diff.ts +52 -0
  109. package/src/errors.ts +39 -0
  110. package/src/exposure.ts +236 -0
  111. package/src/index.ts +66 -0
  112. package/src/leases.ts +337 -0
  113. package/src/providers/macos.ts +185 -0
  114. package/src/providers/native-helper.ts +393 -0
  115. package/src/service.ts +583 -0
  116. package/src/skill.ts +123 -0
  117. package/src/target-resolver.ts +237 -0
  118. package/src/tools.ts +528 -0
  119. package/src/types.ts +299 -0
  120. package/src/web.ts +180 -0
package/src/tools.ts ADDED
@@ -0,0 +1,528 @@
1
+ /** Focused model-facing Computer Use Tool definitions. */
2
+
3
+ import { createUserMessage, type ContentBlock } from '@deepseek-ai/dsh-llm'
4
+ import { defineTool, type JsonValue, type ToolDefinition, type ToolRunContext, type ValueSchemaSpec } from '@deepseek-ai/dsh-tools'
5
+ import {
6
+ ComputerConfirmationToken,
7
+ ComputerObservationId,
8
+ ComputerTargetHandle,
9
+ type ComputerActionRequest,
10
+ type ComputerArtifact,
11
+ type ComputerUseContext,
12
+ } from './types.ts'
13
+ import type { ComputerUseService } from './service.ts'
14
+
15
+ function renderJson(_args: unknown, value: unknown): ContentBlock[] {
16
+ return [{ type: 'text', text: JSON.stringify(value, null, 2) }]
17
+ }
18
+
19
+ function contextOf(exec: ToolRunContext): ComputerUseContext {
20
+ const agent = exec.agent
21
+ if (agent === undefined) throw new Error(`${exec.name}: an Agent Session is required`)
22
+ return {
23
+ agent,
24
+ workspace: agent.session.header.cwd ?? process.cwd(),
25
+ callId: exec.callId,
26
+ signal: exec.signal,
27
+ }
28
+ }
29
+
30
+ function deferVisionHandoff(exec: ToolRunContext, artifact: ComputerArtifact | undefined): void {
31
+ if (artifact === undefined) return
32
+ exec.deferContext(createUserMessage({
33
+ content: [{
34
+ type: 'text',
35
+ text: [
36
+ `Computer Use returned a screenshot Artifact at ${JSON.stringify(artifact.path)}.`,
37
+ 'If the task now needs OCR, visual grounding, or pixel inspection and vision_glance is not visible, call the skill tool with {"name":"vision-tools"}; then pass this exact Artifact path to vision_glance, vision_ground, vision_detect, vision_crop, or vision_long_screenshot_ocr.',
38
+ 'Do not inspect OCR executables or use bash, tesseract, screencapture, or an ad hoc Swift/Python OCR implementation.',
39
+ ].join(' '),
40
+ }],
41
+ source: { kind: 'plugin', plugin: 'dsh-computer-use' },
42
+ }))
43
+ }
44
+
45
+ const rectSchema = {
46
+ type: 'object',
47
+ additionalProperties: false,
48
+ properties: {
49
+ x: { type: 'number', required: true },
50
+ y: { type: 'number', required: true },
51
+ width: { type: 'number', required: true },
52
+ height: { type: 'number', required: true },
53
+ },
54
+ } as const satisfies ValueSchemaSpec
55
+
56
+ const appSelectorSchema = {
57
+ type: 'object',
58
+ additionalProperties: false,
59
+ properties: {
60
+ bundleId: { type: 'string', description: 'Preferred exact macOS bundle identifier.' },
61
+ pid: { type: 'integer', description: 'Exact current process id when already observed.' },
62
+ name: { type: 'string', description: 'Display name accepted only when it resolves uniquely.' },
63
+ },
64
+ } as const satisfies ValueSchemaSpec
65
+
66
+ const appSchema = {
67
+ type: 'object',
68
+ additionalProperties: false,
69
+ properties: {
70
+ bundleId: { type: 'string', required: true },
71
+ pid: { type: 'integer', required: true },
72
+ name: { type: 'string', required: true },
73
+ },
74
+ } as const satisfies ValueSchemaSpec
75
+
76
+ const elementSchema = {
77
+ type: 'object',
78
+ additionalProperties: false,
79
+ properties: {
80
+ index: { type: 'integer', required: true },
81
+ targetHandle: { type: 'string', required: true },
82
+ role: { type: 'string', required: true },
83
+ subrole: { type: 'string' },
84
+ title: { type: 'string' },
85
+ label: { type: 'string' },
86
+ value: { type: 'string' },
87
+ enabled: { type: 'boolean' },
88
+ focused: { type: 'boolean' },
89
+ selected: { type: 'boolean' },
90
+ frame: rectSchema,
91
+ actions: { type: 'array', items: { type: 'string' }, required: true },
92
+ },
93
+ } as const satisfies ValueSchemaSpec
94
+
95
+ const artifactSchema = {
96
+ type: 'object',
97
+ additionalProperties: false,
98
+ properties: {
99
+ path: { type: 'string', required: true },
100
+ filename: { type: 'string', required: true },
101
+ mimeType: { type: 'string', enum: ['image/png'], required: true },
102
+ kind: { type: 'string', enum: ['image'], required: true },
103
+ description: { type: 'string', required: true },
104
+ sourceTool: { type: 'string', enum: ['computer_observe', 'computer_action'], required: true },
105
+ previewIntent: { type: 'string', enum: ['image'], required: true },
106
+ bytes: { type: 'integer', required: true },
107
+ width: { type: 'integer', required: true },
108
+ height: { type: 'integer', required: true },
109
+ },
110
+ } as const satisfies ValueSchemaSpec
111
+
112
+ const observationSchema = {
113
+ type: 'object',
114
+ additionalProperties: false,
115
+ properties: {
116
+ observationId: { type: 'string', required: true },
117
+ app: { ...appSchema, required: true },
118
+ createdAt: { type: 'string', required: true },
119
+ expiresAt: { type: 'string', required: true },
120
+ frontmost: { type: 'boolean', required: true },
121
+ window: {
122
+ type: 'object',
123
+ additionalProperties: false,
124
+ properties: {
125
+ title: { type: 'string' },
126
+ frame: { ...rectSchema, required: true },
127
+ id: { type: 'integer' },
128
+ },
129
+ },
130
+ tree: {
131
+ type: 'object',
132
+ additionalProperties: false,
133
+ required: true,
134
+ properties: {
135
+ mode: { type: 'string', enum: ['full', 'diff'], required: true },
136
+ text: { type: 'string', required: true },
137
+ truncated: { type: 'boolean', required: true },
138
+ },
139
+ },
140
+ elements: { type: 'array', items: elementSchema, required: true },
141
+ screenshot: artifactSchema,
142
+ permissions: {
143
+ type: 'object',
144
+ additionalProperties: false,
145
+ required: true,
146
+ properties: {
147
+ accessibility: { type: 'string', enum: ['granted', 'denied', 'not-determined', 'unavailable'], required: true },
148
+ screenRecording: { type: 'string', enum: ['granted', 'denied', 'not-determined', 'unavailable'], required: true },
149
+ },
150
+ },
151
+ },
152
+ } as const satisfies ValueSchemaSpec
153
+
154
+ const actionResultSchema = {
155
+ type: 'object',
156
+ additionalProperties: false,
157
+ properties: {
158
+ action: { type: 'string', enum: ['click', 'set-value', 'type-text', 'press-key', 'scroll', 'drag', 'perform-action', 'wait'], required: true },
159
+ channel: { type: 'string', enum: ['accessibility', 'coordinates', 'keyboard', 'wait'], required: true },
160
+ activation: { type: 'string', enum: ['not-requested', 'already-frontmost', 'activated'], required: true },
161
+ pointerInput: { type: 'boolean', required: true },
162
+ pointerRouting: { type: 'string', enum: ['none', 'target-process'], required: true },
163
+ resolution: {
164
+ type: 'object',
165
+ additionalProperties: false,
166
+ properties: {
167
+ mode: { type: 'string', enum: ['exact-locator', 'native-identifier', 'semantic-rebind'], required: true },
168
+ confidence: { type: 'number', required: true },
169
+ candidateCount: { type: 'integer', required: true },
170
+ targetChanged: { type: 'boolean', required: true },
171
+ },
172
+ },
173
+ observation: { ...observationSchema, required: true },
174
+ },
175
+ } as const satisfies ValueSchemaSpec
176
+
177
+ const sensitiveParameters = {
178
+ sensitive: { type: 'boolean', description: 'Set true for an action classified by the Skill as high impact or sensitive.' },
179
+ confirmationToken: { type: 'string', description: 'One-use token from computer_confirm for this exact action.' },
180
+ } as const
181
+
182
+ const keyNames = [
183
+ 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', 'c', 'v', 'b', 'q', 'w', 'e', 'r', 'y', 't',
184
+ '1', '2', '3', '4', '6', '5', '=', '9', '7', '-', '8', '0', ']', 'o', 'u', '[', 'i', 'p',
185
+ 'return', 'l', 'j', "'", 'k', ';', '\\', ',', '/', 'n', 'm', '.', 'tab', 'space', 'delete',
186
+ 'escape', 'home', 'pageup', 'forwarddelete', 'end', 'pagedown', 'left', 'right', 'down', 'up',
187
+ ] as const
188
+
189
+ const confirmationActionSchema = {
190
+ oneOf: [
191
+ {
192
+ type: 'object', additionalProperties: false, properties: {
193
+ kind: { type: 'string', const: 'click', required: true },
194
+ observationId: { type: 'string', required: true },
195
+ elementIndex: { type: 'integer' },
196
+ targetHandle: { type: 'string' },
197
+ allowRebind: { type: 'boolean' },
198
+ x: { type: 'number' },
199
+ y: { type: 'number' },
200
+ coordinateSpace: { type: 'string', enum: ['window', 'screen'] },
201
+ button: { type: 'string', enum: ['left', 'right', 'middle'] },
202
+ clickCount: { type: 'integer' },
203
+ allowCoordinateFallback: { type: 'boolean' },
204
+ },
205
+ },
206
+ {
207
+ type: 'object', additionalProperties: false, properties: {
208
+ kind: { type: 'string', const: 'set-value', required: true },
209
+ observationId: { type: 'string', required: true },
210
+ elementIndex: { type: 'integer' },
211
+ targetHandle: { type: 'string' },
212
+ allowRebind: { type: 'boolean' },
213
+ value: { type: 'string', required: true },
214
+ },
215
+ },
216
+ {
217
+ type: 'object', additionalProperties: false, properties: {
218
+ kind: { type: 'string', const: 'type-text', required: true },
219
+ observationId: { type: 'string', required: true },
220
+ text: { type: 'string', required: true },
221
+ },
222
+ },
223
+ {
224
+ type: 'object', additionalProperties: false, properties: {
225
+ kind: { type: 'string', const: 'press-key', required: true },
226
+ observationId: { type: 'string', required: true },
227
+ key: { type: 'string', enum: keyNames, required: true },
228
+ modifiers: { type: 'array', items: { type: 'string', enum: ['command', 'control', 'option', 'shift'] } },
229
+ },
230
+ },
231
+ {
232
+ type: 'object', additionalProperties: false, properties: {
233
+ kind: { type: 'string', const: 'scroll', required: true },
234
+ observationId: { type: 'string', required: true },
235
+ elementIndex: { type: 'integer' },
236
+ targetHandle: { type: 'string' },
237
+ allowRebind: { type: 'boolean' },
238
+ x: { type: 'number' },
239
+ y: { type: 'number' },
240
+ coordinateSpace: { type: 'string', enum: ['window', 'screen'] },
241
+ direction: { type: 'string', enum: ['up', 'down', 'left', 'right'], required: true },
242
+ pages: { type: 'integer' },
243
+ },
244
+ },
245
+ {
246
+ type: 'object', additionalProperties: false, properties: {
247
+ kind: { type: 'string', const: 'drag', required: true },
248
+ observationId: { type: 'string', required: true },
249
+ fromX: { type: 'number', required: true },
250
+ fromY: { type: 'number', required: true },
251
+ toX: { type: 'number', required: true },
252
+ toY: { type: 'number', required: true },
253
+ coordinateSpace: { type: 'string', enum: ['window', 'screen'] },
254
+ },
255
+ },
256
+ {
257
+ type: 'object', additionalProperties: false, properties: {
258
+ kind: { type: 'string', const: 'perform-action', required: true },
259
+ observationId: { type: 'string', required: true },
260
+ elementIndex: { type: 'integer' },
261
+ targetHandle: { type: 'string' },
262
+ allowRebind: { type: 'boolean' },
263
+ action: { type: 'string', required: true },
264
+ },
265
+ },
266
+ ],
267
+ } as const satisfies ValueSchemaSpec
268
+
269
+ function actionBase(args: { observationId: string; sensitive?: boolean; confirmationToken?: string }) {
270
+ return {
271
+ observationId: ComputerObservationId(args.observationId),
272
+ ...(args.sensitive === undefined ? {} : { sensitive: args.sensitive }),
273
+ ...(args.confirmationToken === undefined ? {} : { confirmationToken: ComputerConfirmationToken(args.confirmationToken) }),
274
+ }
275
+ }
276
+
277
+ function elementTarget(args: { elementIndex?: number; targetHandle?: string; allowRebind?: boolean }) {
278
+ return {
279
+ ...(args.elementIndex === undefined ? {} : { elementIndex: args.elementIndex }),
280
+ ...(args.targetHandle === undefined ? {} : { targetHandle: ComputerTargetHandle(args.targetHandle) }),
281
+ ...(args.allowRebind === undefined ? {} : { allowRebind: args.allowRebind }),
282
+ }
283
+ }
284
+
285
+ function artifactPresentation(artifact: ComputerArtifact): JsonValue {
286
+ return {
287
+ path: artifact.path,
288
+ filename: artifact.filename,
289
+ mimeType: artifact.mimeType,
290
+ kind: artifact.kind,
291
+ description: artifact.description,
292
+ sourceTool: artifact.sourceTool,
293
+ previewIntent: artifact.previewIntent,
294
+ bytes: artifact.bytes,
295
+ width: artifact.width,
296
+ height: artifact.height,
297
+ }
298
+ }
299
+
300
+ function actionOutput() {
301
+ return {
302
+ schema: actionResultSchema,
303
+ render: renderJson,
304
+ presentationMeta: (_args: unknown, value: { observation: { screenshot?: ComputerArtifact } }): JsonValue => value.observation.screenshot === undefined
305
+ ? {}
306
+ : { artifacts: [artifactPresentation(value.observation.screenshot)] },
307
+ }
308
+ }
309
+
310
+ /** Create the focused execution definitions bound to one active Service generation. */
311
+ export function createComputerUseTools(service: ComputerUseService): ToolDefinition[] {
312
+ const listApps = defineTool({
313
+ name: 'computer_list_apps',
314
+ description: 'List bounded running user-facing macOS applications. Use this only when the task does not already identify a unique bundle id.',
315
+ parameters: {},
316
+ output: {
317
+ schema: {
318
+ type: 'array',
319
+ items: {
320
+ type: 'object',
321
+ additionalProperties: false,
322
+ properties: {
323
+ ...appSchema.properties,
324
+ frontmost: { type: 'boolean', required: true },
325
+ accessibility: { type: 'string', enum: ['granted', 'denied', 'not-determined', 'unavailable'], required: true },
326
+ screenRecording: { type: 'string', enum: ['granted', 'denied', 'not-determined', 'unavailable'], required: true },
327
+ },
328
+ },
329
+ },
330
+ render: renderJson,
331
+ },
332
+ execute: (_args, exec) => service.listApps(contextOf(exec)),
333
+ presentCall: () => ({ card: 'generic', title: 'List macOS apps', kind: 'read' }),
334
+ })
335
+
336
+ const observe = defineTool({
337
+ name: 'computer_observe',
338
+ description: 'Read a fresh Accessibility observation for one exact running app. Element indexes belong only to the returned observationId. Prefer the tree; request a screenshot for pixel-only facts. When a screenshot needs OCR, visual grounding, or pixel inspection, load the vision-tools Skill and pass the returned Artifact path to its native tools instead of using bash, tesseract, screencapture, or an ad hoc OCR script.',
339
+ parameters: {
340
+ app: { ...appSelectorSchema, required: true },
341
+ screenshot: { type: 'string', enum: ['none', 'optional', 'required'], description: 'Default optional. Required fails when Screen Recording is unavailable.' },
342
+ full: { type: 'boolean', description: 'Return a full tree instead of a diff from the previous observation.' },
343
+ },
344
+ output: {
345
+ schema: observationSchema,
346
+ render: renderJson,
347
+ presentationMeta: (_args, value) => value.screenshot === undefined ? {} : { artifacts: [value.screenshot] },
348
+ },
349
+ execute: async (args, exec) => {
350
+ const observation = await service.observe(args, contextOf(exec))
351
+ deferVisionHandoff(exec, observation.screenshot)
352
+ return observation
353
+ },
354
+ presentCall: () => ({ card: 'generic', title: 'Observe macOS app', kind: 'read' }),
355
+ })
356
+
357
+ const click = defineTool({
358
+ name: 'computer_click',
359
+ description: 'Click an observed element, preferring AXPress without foreground activation, or use a window-relative or screen-global coordinate when host pointer policy allows it. For safe recovery after harmless tree reordering, pass the element targetHandle and allowRebind=true; elementIndex remains exact-observation compatibility. The successful result includes deterministic resolution metadata and a fresh post-click observation.',
360
+ parameters: {
361
+ observationId: { type: 'string', required: true },
362
+ elementIndex: { type: 'integer' },
363
+ targetHandle: { type: 'string', description: 'Opaque handle returned on the selected observation element.' },
364
+ allowRebind: { type: 'boolean', description: 'Allow fail-closed native-identifier or unique semantic rebinding. Requires targetHandle.' },
365
+ x: { type: 'number' },
366
+ y: { type: 'number' },
367
+ coordinateSpace: { type: 'string', enum: ['window', 'screen'], description: 'Default window interprets x/y inside the observed window frame; screen uses Quartz screen-global coordinates.' },
368
+ button: { type: 'string', enum: ['left', 'right', 'middle'] },
369
+ clickCount: { type: 'integer' },
370
+ allowCoordinateFallback: { type: 'boolean' },
371
+ ...sensitiveParameters,
372
+ },
373
+ output: actionOutput(),
374
+ execute: (args, exec) => service.act({ kind: 'click', ...actionBase(args), ...elementTarget(args), ...args.x === undefined ? {} : { x: args.x }, ...args.y === undefined ? {} : { y: args.y }, ...args.coordinateSpace === undefined ? {} : { coordinateSpace: args.coordinateSpace }, ...args.button === undefined ? {} : { button: args.button }, ...args.clickCount === undefined ? {} : { clickCount: args.clickCount }, ...args.allowCoordinateFallback === undefined ? {} : { allowCoordinateFallback: args.allowCoordinateFallback } }, contextOf(exec)),
375
+ presentCall: () => ({ card: 'generic', title: 'Click macOS app', kind: 'execute' }),
376
+ })
377
+
378
+ const setValue = defineTool({
379
+ name: 'computer_set_value',
380
+ description: 'Set one observed editable Accessibility value without using the clipboard. Supply elementIndex or targetHandle; targetHandle plus allowRebind=true permits deterministic fail-closed recovery after harmless tree reordering.',
381
+ parameters: {
382
+ observationId: { type: 'string', required: true },
383
+ elementIndex: { type: 'integer' },
384
+ targetHandle: { type: 'string' },
385
+ allowRebind: { type: 'boolean' },
386
+ value: { type: 'string', required: true },
387
+ ...sensitiveParameters,
388
+ },
389
+ output: actionOutput(),
390
+ execute: (args, exec) => service.act({ kind: 'set-value', ...actionBase(args), ...elementTarget(args), value: args.value }, contextOf(exec)),
391
+ presentCall: () => ({ card: 'generic', title: 'Set app value', kind: 'execute' }),
392
+ })
393
+
394
+ const typeText = defineTool({
395
+ name: 'computer_type_text',
396
+ description: 'Type Unicode into the currently focused control without reading or replacing the clipboard. Focus a control using fresh state first; keyboard fallback may require host-authorized foreground activation. The result does not echo the supplied text.',
397
+ parameters: {
398
+ observationId: { type: 'string', required: true },
399
+ text: { type: 'string', required: true },
400
+ ...sensitiveParameters,
401
+ },
402
+ output: actionOutput(),
403
+ execute: (args, exec) => service.act({ kind: 'type-text', ...actionBase(args), text: args.text }, contextOf(exec)),
404
+ presentCall: () => ({ card: 'generic', title: 'Type in macOS app', kind: 'execute' }),
405
+ })
406
+
407
+ const pressKey = defineTool({
408
+ name: 'computer_press_key',
409
+ description: 'Press one validated key or chord by routing it to the selected app process. The default host policy preserves the current foreground app; use documented key names and read the returned fresh observation.',
410
+ parameters: {
411
+ observationId: { type: 'string', required: true },
412
+ key: { type: 'string', enum: keyNames, required: true },
413
+ modifiers: { type: 'array', items: { type: 'string', enum: ['command', 'control', 'option', 'shift'] } },
414
+ ...sensitiveParameters,
415
+ },
416
+ output: actionOutput(),
417
+ execute: (args, exec) => service.act({ kind: 'press-key', ...actionBase(args), key: args.key, ...args.modifiers === undefined ? {} : { modifiers: args.modifiers } }, contextOf(exec)),
418
+ presentCall: () => ({ card: 'generic', title: 'Press app key', kind: 'execute' }),
419
+ })
420
+
421
+ const scroll = defineTool({
422
+ name: 'computer_scroll',
423
+ description: 'Scroll by routing a wheel event only to the selected app process at an observed element or window-relative/screen-global coordinate. The system cursor is not moved.',
424
+ parameters: {
425
+ observationId: { type: 'string', required: true },
426
+ elementIndex: { type: 'integer' },
427
+ targetHandle: { type: 'string' },
428
+ allowRebind: { type: 'boolean' },
429
+ x: { type: 'number' },
430
+ y: { type: 'number' },
431
+ coordinateSpace: { type: 'string', enum: ['window', 'screen'], description: 'Default window interprets x/y inside the observed window frame; screen uses Quartz screen-global coordinates.' },
432
+ direction: { type: 'string', enum: ['up', 'down', 'left', 'right'], required: true },
433
+ pages: { type: 'integer' },
434
+ ...sensitiveParameters,
435
+ },
436
+ output: actionOutput(),
437
+ execute: (args, exec) => service.act({ kind: 'scroll', ...actionBase(args), ...elementTarget(args), direction: args.direction, ...args.x === undefined ? {} : { x: args.x }, ...args.y === undefined ? {} : { y: args.y }, ...args.coordinateSpace === undefined ? {} : { coordinateSpace: args.coordinateSpace }, ...args.pages === undefined ? {} : { pages: args.pages } }, contextOf(exec)),
438
+ presentCall: () => ({ card: 'generic', title: 'Scroll macOS app', kind: 'execute' }),
439
+ })
440
+
441
+ const drag = defineTool({
442
+ name: 'computer_drag',
443
+ description: 'Drag by routing mouse events only to the selected app process between two points in the observed-window or screen-global coordinate space. The system cursor is not moved.',
444
+ parameters: {
445
+ observationId: { type: 'string', required: true },
446
+ fromX: { type: 'number', required: true },
447
+ fromY: { type: 'number', required: true },
448
+ toX: { type: 'number', required: true },
449
+ toY: { type: 'number', required: true },
450
+ coordinateSpace: { type: 'string', enum: ['window', 'screen'], description: 'Default window interprets the points inside the observed window frame; screen uses Quartz screen-global coordinates.' },
451
+ ...sensitiveParameters,
452
+ },
453
+ output: actionOutput(),
454
+ execute: (args, exec) => service.act({ kind: 'drag', ...actionBase(args), fromX: args.fromX, fromY: args.fromY, toX: args.toX, toY: args.toY, ...args.coordinateSpace === undefined ? {} : { coordinateSpace: args.coordinateSpace } }, contextOf(exec)),
455
+ presentCall: () => ({ card: 'generic', title: 'Drag in macOS app', kind: 'execute' }),
456
+ })
457
+
458
+ const perform = defineTool({
459
+ name: 'computer_perform_action',
460
+ description: 'Perform one Accessibility action advertised by an observed element. Supply elementIndex or targetHandle; targetHandle plus allowRebind=true permits deterministic fail-closed recovery after harmless tree reordering.',
461
+ parameters: {
462
+ observationId: { type: 'string', required: true },
463
+ elementIndex: { type: 'integer' },
464
+ targetHandle: { type: 'string' },
465
+ allowRebind: { type: 'boolean' },
466
+ action: { type: 'string', required: true },
467
+ ...sensitiveParameters,
468
+ },
469
+ output: actionOutput(),
470
+ execute: (args, exec) => service.act({ kind: 'perform-action', ...actionBase(args), ...elementTarget(args), action: args.action }, contextOf(exec)),
471
+ presentCall: () => ({ card: 'generic', title: 'Perform app action', kind: 'execute' }),
472
+ })
473
+
474
+ const wait = defineTool({
475
+ name: 'computer_wait',
476
+ description: 'Wait for one bounded Accessibility condition and return fresh state without mutating the app.',
477
+ parameters: {
478
+ observationId: { type: 'string', required: true },
479
+ condition: {
480
+ type: 'object',
481
+ additionalProperties: false,
482
+ required: true,
483
+ properties: {
484
+ text: { type: 'string' },
485
+ elementRole: { type: 'string' },
486
+ elementTitle: { type: 'string' },
487
+ },
488
+ },
489
+ timeoutMs: { type: 'integer' },
490
+ },
491
+ output: actionOutput(),
492
+ execute: (args, exec) => service.act({ kind: 'wait', observationId: ComputerObservationId(args.observationId), condition: args.condition, ...args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs } }, contextOf(exec)),
493
+ presentCall: () => ({ card: 'generic', title: 'Wait for app state', kind: 'read' }),
494
+ })
495
+
496
+ const confirm = defineTool({
497
+ name: 'computer_confirm',
498
+ description: 'Request just-in-time approval for one exact sensitive action. Call immediately before the action, then repeat the same action with sensitive=true and the returned token.',
499
+ parameters: {
500
+ action: { ...confirmationActionSchema, required: true },
501
+ reason: { type: 'string', required: true },
502
+ target: { type: 'string', required: true },
503
+ dataSummary: { type: 'string' },
504
+ },
505
+ output: {
506
+ schema: {
507
+ type: 'object',
508
+ additionalProperties: false,
509
+ properties: {
510
+ token: { type: 'string', required: true },
511
+ observationId: { type: 'string', required: true },
512
+ app: { ...appSchema, required: true },
513
+ expiresAt: { type: 'string', required: true },
514
+ },
515
+ },
516
+ render: renderJson,
517
+ },
518
+ execute: (args, exec) => service.confirm({
519
+ action: { ...args.action, observationId: ComputerObservationId(args.action.observationId), sensitive: true } as ComputerActionRequest,
520
+ reason: args.reason,
521
+ target: args.target,
522
+ ...(args.dataSummary === undefined ? {} : { dataSummary: args.dataSummary }),
523
+ }, contextOf(exec)),
524
+ presentCall: () => ({ card: 'generic', title: 'Confirm sensitive app action', kind: 'execute' }),
525
+ })
526
+
527
+ return [listApps, observe, click, setValue, typeText, pressKey, scroll, drag, perform, wait, confirm]
528
+ }