dsh-codex-style-computer-use 2.0.0 → 2.2.3
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.ar.md +234 -0
- package/README.de.md +234 -0
- package/README.es.md +264 -0
- package/README.fa.md +234 -0
- package/README.fr.md +243 -0
- package/README.md +45 -8
- package/README.pt.md +234 -0
- package/README.ru.md +259 -0
- package/README.zh.md +45 -8
- package/client.js +130 -0
- package/icon.svg +20 -4
- package/lib/bin/attention-worker.exe +0 -0
- package/lib/bin/attention-worker.exe.json +1 -1
- package/lib/core/bin/dsh-computer-use-worker.exe +0 -0
- package/lib/core/bin/dsh-computer-use-worker.exe.json +2 -2
- package/lib/core/index.js +51 -3
- package/lib/core/worker.cs +467 -18
- package/lib/host-debug.js +88 -0
- package/lib/image-output.js +28 -6
- package/lib/index.js +104 -0
- package/lib/launch.js +67 -0
- package/lib/policy.js +22 -2
- package/lib/tools.js +74 -8
- package/package.json +101 -91
- package/scripts/check-behavior.mjs +1 -1
- package/scripts/verify-package.mjs +9 -0
- package/skills/computer-use/SKILL.md +59 -50
package/lib/core/index.js
CHANGED
|
@@ -119,7 +119,11 @@ export class ComputerUse extends EventEmitter {
|
|
|
119
119
|
// safety knobs (sandraschi SAFETY.md four-piece: HITL + kill switch +
|
|
120
120
|
// rate limit + audit; see DESIGN §8)
|
|
121
121
|
this.dryRun = opts.dryRun ?? false // simulate actuations, log only
|
|
122
|
-
this.kickoffRequired = opts.kickoffRequired !== false // pure observation must not require focusing
|
|
122
|
+
this.kickoffRequired = opts.kickoffRequired !== false // pure observation must not require focusing
|
|
123
|
+
// ---- host-driving debug gates (lib/host-debug.js): two independent switches, both fail closed
|
|
124
|
+
this.hostDebugFile = typeof opts.hostDebugFile === 'string' ? opts.hostDebugFile : ''
|
|
125
|
+
this.hostGate = typeof opts.hostGate === 'function' ? opts.hostGate : null
|
|
126
|
+
this.onHostBlocked = typeof opts.onHostBlocked === 'function' ? opts.onHostBlocked : null
|
|
123
127
|
this.rateLimit = opts.rateLimit ?? { max: 60, windowMs: 60_000 }
|
|
124
128
|
this.rateWindow = []
|
|
125
129
|
this.humanApproved = opts.autoApprove ?? false // gate for mutating ops
|
|
@@ -171,7 +175,8 @@ export class ComputerUse extends EventEmitter {
|
|
|
171
175
|
this.spawnRuntime = nativeRuntimeIdentity(this.workerExe)
|
|
172
176
|
const env = { ...process.env, DSH_CU_HOST_PID: String(process.pid), DSH_CU_HOST_EXE: process.execPath || '',
|
|
173
177
|
DSH_CU_PACKAGE_VERSION: this.spawnRuntime.packageVersion, DSH_CU_BUILD_ID: this.spawnRuntime.buildId,
|
|
174
|
-
DSH_CU_SOURCE_HASH: this.spawnRuntime.sourceHash || '', DSH_CU_BINARY_HASH: this.spawnRuntime.binaryHash
|
|
178
|
+
DSH_CU_SOURCE_HASH: this.spawnRuntime.sourceHash || '', DSH_CU_BINARY_HASH: this.spawnRuntime.binaryHash,
|
|
179
|
+
...(this.hostDebugFile ? { DSH_CU_HOST_DEBUG_FILE: this.hostDebugFile } : {}) }
|
|
175
180
|
this.log(`[core] host guard armed for pid ${process.pid} (${process.execPath})`)
|
|
176
181
|
this.proc = spawn(this.workerExe, [], { env, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] })
|
|
177
182
|
// A CHILD THAT FAILS TO START MUST NAME ITSELF (2026-09-13). Every computer_* tool died with a
|
|
@@ -322,6 +327,8 @@ export class ComputerUse extends EventEmitter {
|
|
|
322
327
|
msg.outcome === 'not-dispatched' ? 'not-dispatched' : 'unknown')
|
|
323
328
|
if (msg.glowHealth) error.glowHealth = msg.glowHealth
|
|
324
329
|
for (const key of ['dragStarted', 'expectedCursor', 'actualCursor', 'cursorCheck']) if (msg[key] !== undefined) error[key] = msg[key]
|
|
330
|
+
// A gate refusal is an ACTIONABLE state, not a crash: the settings page surfaces the last one.
|
|
331
|
+
if (/HOST_DEBUG_GATE/.test(String(error.message || ''))) { try { this.onHostBlocked?.() } catch { } }
|
|
325
332
|
p.reject(error.code === 'HUMAN_REOBSERVE' ? humanReobserveError(error) : error)
|
|
326
333
|
}
|
|
327
334
|
}
|
|
@@ -400,7 +407,17 @@ export class ComputerUse extends EventEmitter {
|
|
|
400
407
|
})
|
|
401
408
|
// __cycle is signed last. A caller cannot forge another input series or turn.
|
|
402
409
|
const signed = { ...args, __cycle: context?.internalCleanup !== true, __kickoff: this.kickoffRequired }
|
|
403
|
-
if (input) signed.__inputSeries = context?.inputSeries || randomUUID()
|
|
410
|
+
if (input) signed.__inputSeries = context?.inputSeries || randomUUID()
|
|
411
|
+
// The two host-driving gates travel with EVERY call so the worker can decide per request:
|
|
412
|
+
// gate 1 = the calling conversation is full permission, gate 2 = debug mode was switched on by
|
|
413
|
+
// hand. Both unknown/absent => false (fail closed). Tools cannot forge these: they are stamped
|
|
414
|
+
// here, after the caller's args, exactly like __cycle.
|
|
415
|
+
if (this.hostGate) {
|
|
416
|
+
let gate = null
|
|
417
|
+
try { gate = this.hostGate(context) } catch { gate = null }
|
|
418
|
+
signed.__hostDebug = !!(gate && gate.debug === true)
|
|
419
|
+
signed.__hostFull = !!(gate && gate.full === true)
|
|
420
|
+
}
|
|
404
421
|
return new Promise((resolve, reject) => {
|
|
405
422
|
let written = false
|
|
406
423
|
const read = UIA_READS.has(op) && killOnTimeout ? (this.uiaRead = { id, child, timedOut: false }) : null
|
|
@@ -719,6 +736,11 @@ export class ComputerUse extends EventEmitter {
|
|
|
719
736
|
x: pt.x, y: pt.y, button: opts.button || 'left', clicks: opts.clicks || 1,
|
|
720
737
|
settleMs: opts.settleMs ?? 40,
|
|
721
738
|
...(witness ? { verificationRegion: witness } : {}),
|
|
739
|
+
// Embedded renderers (a WeChat article panel, an Electron child) belong to a DIFFERENT
|
|
740
|
+
// process than the window that hosts them, so the occlusion check reads them as "covered"
|
|
741
|
+
// even though the pixel is inside the app the caller activated. The worker already
|
|
742
|
+
// implements the escape hatch; forward it, or a whole class of real apps is unclickable.
|
|
743
|
+
...(opts.allowCovered ? { allowCovered: true } : {}),
|
|
722
744
|
...((pt.expectHwnd || opts.expectHwnd) ? { expectHwnd: pt.expectHwnd || opts.expectHwnd } : {}),
|
|
723
745
|
})
|
|
724
746
|
}
|
|
@@ -1031,6 +1053,32 @@ export class ComputerUse extends EventEmitter {
|
|
|
1031
1053
|
async ocr (region) { return this.call('ocr', region ? { ...region } : {}) }
|
|
1032
1054
|
async clipWrite (text) { return this.actuate('clipWrite', { text }) }
|
|
1033
1055
|
|
|
1056
|
+
/** Open an app/URL/folder/document through ShellExecute, as a real actuation: the halo,
|
|
1057
|
+
* the brake checks, the audit trail and the rate window all come from actuate() — which
|
|
1058
|
+
* is exactly why this is an op and not a Node-side spawn (see worker.cs Launch). */
|
|
1059
|
+
async launch (target, opts = {}) {
|
|
1060
|
+
// `args` is an ARRAY of argument values (quoted once, in the worker). Passing a ready-made
|
|
1061
|
+
// command line would make the launcher parse our boundaries differently than the caller meant.
|
|
1062
|
+
return this.actuate('launch', {
|
|
1063
|
+
target,
|
|
1064
|
+
...(opts.args !== undefined ? { args: opts.args } : {}),
|
|
1065
|
+
...(opts.tailMs !== undefined ? { tailMs: opts.tailMs } : {}),
|
|
1066
|
+
})
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/** Put keyboard focus on a FIELD and prove it: clicks (x,y), then reports the UIA focused
|
|
1070
|
+
* element and whether a caret is provable there (OS caret, else a two-frame blink check).
|
|
1071
|
+
* Refuse to type when `verified` is false — typing follows the focused control, not the
|
|
1072
|
+
* window, and a document that kept focus silently swallows the text. */
|
|
1073
|
+
async focusField (opts = {}) {
|
|
1074
|
+
return this.actuate('focusField', {
|
|
1075
|
+
x: opts.x,
|
|
1076
|
+
y: opts.y,
|
|
1077
|
+
...(opts.hwnd !== undefined ? { hwnd: opts.hwnd } : {}),
|
|
1078
|
+
...(opts.verifyCaret !== undefined ? { verifyCaret: opts.verifyCaret } : {}),
|
|
1079
|
+
})
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1034
1082
|
async activateWindow (sel) { return this.actuate('activate', sel) }
|
|
1035
1083
|
async windowOp (sel, op, opts = {}) { return this.actuate('windowOp', { ...sel, op, ...opts }) }
|
|
1036
1084
|
/** Deterministic placement (no mouse): move/resize a window to an arbitrary rect. */
|