@anionex/dsh-computer-use 0.2.1 → 0.3.1
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.
- package/README.md +13 -7
- package/README.zh.md +13 -7
- package/cordis.patch.yml +3 -0
- package/docs/interaction-policy.md +6 -4
- package/docs/interaction-policy.zh.md +7 -5
- package/lib/client.js +31 -11
- package/lib/client.js.map +1 -1
- package/lib/config.js +13 -3
- package/lib/config.js.map +1 -1
- package/lib/providers/macos.js +79 -14
- package/lib/providers/macos.js.map +1 -1
- package/lib/providers/native-helper.js +240 -17
- package/lib/providers/native-helper.js.map +1 -1
- package/lib/providers/unsupported.js +6 -0
- package/lib/providers/unsupported.js.map +1 -1
- package/lib/service.js +74 -32
- package/lib/service.js.map +1 -1
- package/lib/types/backend.d.ts +18 -3
- package/lib/types/backend.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +7 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/config.d.ts +7 -1
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/providers/macos.d.ts +8 -2
- package/lib/types/providers/macos.d.ts.map +1 -1
- package/lib/types/providers/native-helper.d.ts +9 -1
- package/lib/types/providers/native-helper.d.ts.map +1 -1
- package/lib/types/providers/unsupported.d.ts +6 -2
- package/lib/types/providers/unsupported.d.ts.map +1 -1
- package/lib/types/service.d.ts.map +1 -1
- package/native/macos/Sources/Fixture/main.swift +46 -0
- package/native/macos/Sources/Helper/CursorMotion.swift +67 -0
- package/native/macos/Sources/Helper/CursorOverlay.swift +334 -60
- package/native/macos/Sources/Helper/TargetedPointer.swift +23 -10
- package/native/macos/Sources/Helper/main.swift +80 -5
- package/native/macos/Sources/Monitor/main.swift +15 -1
- package/native/macos/bin/dsh-computer-use-helper +0 -0
- package/native/macos/manifest.json +3 -3
- package/package.json +16 -18
- package/scripts/build-client.mjs +0 -0
- package/scripts/build-native.mjs +0 -0
- package/scripts/model-e2e.mjs +0 -0
- package/scripts/validate.mjs +0 -0
- package/src/backend.ts +18 -3
- package/src/client/index.tsx +50 -13
- package/src/config.ts +20 -4
- package/src/providers/macos.ts +102 -14
- package/src/providers/native-helper.ts +234 -17
- package/src/providers/unsupported.ts +11 -1
- package/src/service.ts +100 -37
package/src/service.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { randomUUID } from 'node:crypto'
|
|
|
4
4
|
import { setTimeout as delay } from 'node:timers/promises'
|
|
5
5
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
6
6
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
7
|
-
import type { BackendCursorAction, BackendHealth, BackendObservation, ComputerUseBackend, CursorVisibility } from './backend.ts'
|
|
7
|
+
import type { BackendActionResult, BackendCursorAction, BackendHealth, BackendObservation, ComputerUseBackend, CursorVisibility } from './backend.ts'
|
|
8
8
|
import { allocateScreenshotPath, describeScreenshot } from './artifacts.ts'
|
|
9
9
|
import type { ResolvedComputerUseConfig } from './config.ts'
|
|
10
10
|
import { ComputerConfirmationManager } from './confirmations.ts'
|
|
@@ -124,7 +124,6 @@ function requiresPointerInput(
|
|
|
124
124
|
case 'click':
|
|
125
125
|
if (action.x !== undefined || action.y !== undefined) return true
|
|
126
126
|
return element !== undefined
|
|
127
|
-
&& !element.actions.includes('AXPress')
|
|
128
127
|
&& action.allowCoordinateFallback === true
|
|
129
128
|
case 'scroll':
|
|
130
129
|
case 'drag': return true
|
|
@@ -364,75 +363,139 @@ export class ComputerUseService extends Service {
|
|
|
364
363
|
)
|
|
365
364
|
}
|
|
366
365
|
await this.leases.ensure(context.agent, stored.backend.app, 'control', `computer_${action.kind}`, context.callId, signal)
|
|
366
|
+
const cursorRequested = this.config.interaction.cursorVisualization === 'visible'
|
|
367
|
+
&& (action.kind === 'click' || action.kind === 'scroll' || action.kind === 'drag')
|
|
368
|
+
const observationOptions = {
|
|
369
|
+
screenshot: 'none' as const,
|
|
370
|
+
maxNodes: this.config.maxNodes,
|
|
371
|
+
maxDepth: this.config.maxDepth,
|
|
372
|
+
maxTextBytes: this.config.maxTextBytes,
|
|
373
|
+
}
|
|
367
374
|
let actionObservation = stored.backend
|
|
375
|
+
let cursorActivation: 'already-frontmost' | 'activated' | undefined
|
|
368
376
|
let element = selectedOriginalElement
|
|
369
377
|
let resolution: ComputerTargetResolutionResult | undefined = selectedOriginalElement === undefined
|
|
370
378
|
? undefined
|
|
371
379
|
: { mode: 'exact-locator', confidence: 1, candidateCount: 1, targetChanged: false }
|
|
372
380
|
if (descriptor !== undefined) {
|
|
373
|
-
const fresh =
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
maxDepth: this.config.maxDepth,
|
|
377
|
-
maxTextBytes: this.config.maxTextBytes,
|
|
378
|
-
}, signal)
|
|
381
|
+
const fresh = actionObservation === stored.backend
|
|
382
|
+
? await this.backend.observe(stored.backend.app, observationOptions, signal)
|
|
383
|
+
: actionObservation
|
|
379
384
|
const resolved = resolveComputerTarget(stored.backend, fresh, descriptor, allowsTargetRebind(action))
|
|
380
385
|
actionObservation = resolved.observation
|
|
381
386
|
element = resolved.element
|
|
382
387
|
resolution = resolved.resolution
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
388
|
+
}
|
|
389
|
+
if (action.sensitive === true && resolution?.targetChanged === true) {
|
|
390
|
+
this.confirmations.invalidate(context.agent, action.confirmationToken)
|
|
391
|
+
throw new ComputerUseError(
|
|
392
|
+
'COMPUTER_TARGET_REBIND_REQUIRES_CONFIRMATION',
|
|
393
|
+
'the sensitive target rebound to a fresh element; observe the current UI and request a new one-use confirmation before acting',
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
if (cursorRequested
|
|
397
|
+
&& this.config.interaction.focusPolicy === 'activate'
|
|
398
|
+
&& requiresPointerInput(action, selectedOriginalElement)
|
|
399
|
+
&& !actionObservation.frontmost) {
|
|
400
|
+
const activated = await this.backend.activateForCursor(
|
|
401
|
+
stored.backend.app,
|
|
402
|
+
actionObservation.stateHash,
|
|
403
|
+
observationOptions,
|
|
404
|
+
signal,
|
|
405
|
+
)
|
|
406
|
+
actionObservation = activated.observation
|
|
407
|
+
cursorActivation = activated.activation
|
|
408
|
+
if (descriptor !== undefined) {
|
|
409
|
+
const resolved = resolveComputerTarget(stored.backend, actionObservation, descriptor, allowsTargetRebind(action))
|
|
410
|
+
actionObservation = resolved.observation
|
|
411
|
+
element = resolved.element
|
|
412
|
+
resolution = resolved.resolution
|
|
389
413
|
}
|
|
390
414
|
}
|
|
391
|
-
|
|
415
|
+
if (action.sensitive === true && resolution?.targetChanged === true) {
|
|
416
|
+
this.confirmations.invalidate(context.agent, action.confirmationToken)
|
|
417
|
+
throw new ComputerUseError(
|
|
418
|
+
'COMPUTER_TARGET_REBIND_REQUIRES_CONFIRMATION',
|
|
419
|
+
'the sensitive target rebound to a fresh element; observe the current UI and request a new one-use confirmation before acting',
|
|
420
|
+
)
|
|
421
|
+
}
|
|
392
422
|
const visualization = cursorAction(action, element, actionObservation.window, actionObservation.app)
|
|
393
|
-
const cursorRequested = this.config.interaction.cursorVisualization === 'visible'
|
|
394
|
-
&& (action.kind === 'click' || action.kind === 'scroll' || action.kind === 'drag')
|
|
395
423
|
let cursorStarted = false
|
|
396
|
-
//
|
|
397
|
-
//
|
|
424
|
+
// Background targets intentionally hide the overlay. Any other pre-action
|
|
425
|
+
// failure would violate move-before-input ordering and therefore fails closed.
|
|
398
426
|
let cursorState: CursorVisibility | undefined
|
|
399
427
|
const recordCursor = (next: CursorVisibility): void => {
|
|
400
428
|
if (cursorState === undefined || (cursorState.visible && !next.visible)) cursorState = next
|
|
401
429
|
}
|
|
402
430
|
if (cursorRequested && visualization === undefined) {
|
|
403
|
-
|
|
431
|
+
const unavailable: CursorVisibility = {
|
|
404
432
|
visible: false,
|
|
405
433
|
reason: actionObservation.window?.id === undefined
|
|
406
434
|
? 'the agent cursor could not be bound because the target window has no stable window id'
|
|
407
435
|
: 'the agent cursor could not be placed because this action has no observable cursor position',
|
|
408
|
-
|
|
436
|
+
...(!actionObservation.frontmost ? { reasonCode: 'target-not-frontmost' as const } : {}),
|
|
437
|
+
}
|
|
438
|
+
recordCursor(unavailable)
|
|
439
|
+
if (actionObservation.frontmost) {
|
|
440
|
+
throw new ComputerUseError(
|
|
441
|
+
'COMPUTER_PROVIDER_FAILURE',
|
|
442
|
+
`the agent cursor could not complete before the action: ${unavailable.reason}`,
|
|
443
|
+
)
|
|
444
|
+
}
|
|
409
445
|
} else if (visualization !== undefined && cursorRequested) {
|
|
446
|
+
let beforeCursor: CursorVisibility
|
|
410
447
|
try {
|
|
411
|
-
|
|
412
|
-
cursorStarted = true
|
|
448
|
+
beforeCursor = await this.backend.visualizeCursor(visualization, 'before', signal)
|
|
413
449
|
} catch (error) {
|
|
414
|
-
|
|
415
|
-
visible: false,
|
|
416
|
-
reason: `the agent cursor could not be driven before the action: ${error instanceof Error ? error.message : String(error)}`,
|
|
417
|
-
})
|
|
450
|
+
throw computerUseError(error, 'the agent cursor could not be driven before the action')
|
|
418
451
|
}
|
|
452
|
+
recordCursor(beforeCursor)
|
|
453
|
+
const intentionalBackground = beforeCursor.reasonCode === 'target-not-frontmost'
|
|
454
|
+
&& (this.config.interaction.focusPolicy === 'preserve'
|
|
455
|
+
|| !requiresPointerInput(action, selectedOriginalElement))
|
|
456
|
+
if (!beforeCursor.visible && !intentionalBackground) {
|
|
457
|
+
throw new ComputerUseError(
|
|
458
|
+
'COMPUTER_PROVIDER_FAILURE',
|
|
459
|
+
`the agent cursor could not complete before the action: ${beforeCursor.reason ?? 'the overlay is unavailable'}`,
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
cursorStarted = beforeCursor.visible
|
|
463
|
+
}
|
|
464
|
+
let outcome: BackendActionResult
|
|
465
|
+
const request = {
|
|
466
|
+
action,
|
|
467
|
+
app: actionObservation.app,
|
|
468
|
+
expectedStateHash: actionObservation.stateHash,
|
|
469
|
+
interaction: this.config.interaction,
|
|
470
|
+
...(element === undefined ? {} : { element }),
|
|
471
|
+
...(actionObservation.window === undefined ? {} : { window: actionObservation.window }),
|
|
419
472
|
}
|
|
420
|
-
let outcome
|
|
421
473
|
try {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
474
|
+
this.confirmations.consume(context.agent, stored.backend.app, action)
|
|
475
|
+
if (cursorStarted && visualization?.kind === 'drag') {
|
|
476
|
+
const tracked = await this.backend.actDragWithCursor(
|
|
477
|
+
request,
|
|
478
|
+
visualization as BackendCursorAction & { kind: 'drag' },
|
|
479
|
+
signal,
|
|
480
|
+
)
|
|
481
|
+
recordCursor(tracked.cursor)
|
|
482
|
+
outcome = tracked.action
|
|
483
|
+
} else {
|
|
484
|
+
outcome = await this.backend.act(request, signal)
|
|
485
|
+
}
|
|
486
|
+
if (cursorActivation === 'activated' && outcome.activation !== 'activated') {
|
|
487
|
+
outcome = { ...outcome, activation: 'activated' }
|
|
488
|
+
}
|
|
430
489
|
} catch (error) {
|
|
431
490
|
throw computerUseError(error, `Computer Use ${action.kind} failed`)
|
|
432
491
|
} finally {
|
|
433
492
|
if (cursorStarted && visualization !== undefined) {
|
|
434
493
|
try {
|
|
435
|
-
recordCursor(await this.backend.visualizeCursor(
|
|
494
|
+
recordCursor(await this.backend.visualizeCursor(
|
|
495
|
+
visualization,
|
|
496
|
+
'after',
|
|
497
|
+
AbortSignal.timeout(1_500),
|
|
498
|
+
))
|
|
436
499
|
} catch (error) {
|
|
437
500
|
recordCursor({
|
|
438
501
|
visible: false,
|