automify 0.2.0 → 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.
@@ -36,7 +36,7 @@ const DEFAULT_INSTRUCTIONS = [
36
36
  "If the target is not visible, choose a deterministic recovery path: direct URL, terminal command, launcher/search, in-app search, visible navigation, or a screenshot/wait when loading is visible. Do not repeat nearly identical clicks after no visible change.",
37
37
  "After any action that launches an app, navigates, submits input, changes windows, or might trigger loading, use the next screenshot to decide the next step. Stop when the requested result is known; do not keep interacting to confirm unnecessarily."
38
38
  ].join("\n");
39
- const VIRTUAL_DESKTOP_OPTION_KEYS = new Set([
39
+ const DOCKER_DESKTOP_OPTION_KEYS = new Set([
40
40
  "preset",
41
41
  "container",
42
42
  "dockerCommand",
@@ -90,8 +90,8 @@ const VIRTUAL_DESKTOP_OPTION_KEYS = new Set([
90
90
  "logFile",
91
91
  "onUnknownAction"
92
92
  ]);
93
- export const DOCKER_DESKTOP_COMPUTER_OPTION_KEYS = VIRTUAL_DESKTOP_OPTION_KEYS;
94
- const VIRTUAL_DESKTOP_CONTAINER_KEYS = new Set([
93
+ export const DOCKER_DESKTOP_COMPUTER_OPTION_KEYS = DOCKER_DESKTOP_OPTION_KEYS;
94
+ const DOCKER_DESKTOP_CONTAINER_KEYS = new Set([
95
95
  "docker",
96
96
  "dockerCommand",
97
97
  "image",
@@ -120,7 +120,7 @@ const VIRTUAL_DESKTOP_CONTAINER_KEYS = new Set([
120
120
  "additionalAptPackages",
121
121
  "installDependencies"
122
122
  ]);
123
- const VIRTUAL_DESKTOP_DESKTOP_KEYS = new Set([
123
+ const DOCKER_DESKTOP_DESKTOP_KEYS = new Set([
124
124
  "startupCommand",
125
125
  "windowManagerCommand",
126
126
  "packages",
@@ -514,9 +514,6 @@ export class DockerDesktopSession {
514
514
  }
515
515
  }
516
516
 
517
- export const createVirtualDesktopComputer = createDockerDesktopComputer;
518
- export const DockerVirtualDesktopSession = DockerDesktopSession;
519
-
520
517
  async function acquireDockerDesktopLock(options) {
521
518
  if (!options.containerName) return null;
522
519
  return acquireAdapterLock(`docker-desktop:${options.containerName}`, {
@@ -525,9 +522,9 @@ async function acquireDockerDesktopLock(options) {
525
522
  }
526
523
 
527
524
  function normalizeVirtualDesktopOptions(options = {}) {
528
- assertKnownOptions("Docker desktop adapter", options, VIRTUAL_DESKTOP_OPTION_KEYS);
529
- assertKnownOptions("Docker desktop container", options.container, VIRTUAL_DESKTOP_CONTAINER_KEYS);
530
- assertKnownOptions("Docker desktop desktop", options.desktop, VIRTUAL_DESKTOP_DESKTOP_KEYS);
525
+ assertKnownOptions("Docker desktop adapter", options, DOCKER_DESKTOP_OPTION_KEYS);
526
+ assertKnownOptions("Docker desktop container", options.container, DOCKER_DESKTOP_CONTAINER_KEYS);
527
+ assertKnownOptions("Docker desktop desktop", options.desktop, DOCKER_DESKTOP_DESKTOP_KEYS);
531
528
  options = applyDockerDesktopPreset(options);
532
529
  const container = options.container ?? {};
533
530
  const viewport = options.viewport ?? {};
@@ -604,9 +601,6 @@ export function defaultDockerDesktopImage() {
604
601
  return DEFAULT_IMAGE;
605
602
  }
606
603
 
607
- export const virtualDesktopDockerfile = dockerDesktopDockerfile;
608
- export const defaultVirtualDesktopImage = defaultDockerDesktopImage;
609
-
610
604
  function keys(values) {
611
605
  const normalized = values.map((value) => key(value)).filter(Boolean);
612
606
  if (normalized.length === 0) {
@@ -76,7 +76,9 @@ export async function fileToEvaluate(file, options = {}) {
76
76
  truncated ? `Content truncated to ${maxBytes} bytes from ${buffer.byteLength} bytes.` : null,
77
77
  "",
78
78
  text
79
- ].filter((part) => part != null).join("\n")
79
+ ]
80
+ .filter((part) => part != null)
81
+ .join("\n")
80
82
  };
81
83
  }
82
84
 
@@ -126,11 +128,9 @@ function mediaTypeForPath(path) {
126
128
  }
127
129
 
128
130
  function isTextMediaType(mediaType) {
129
- return mediaType.startsWith("text/") || [
130
- "application/json",
131
- "application/xml",
132
- "application/yaml"
133
- ].includes(mediaType);
131
+ return (
132
+ mediaType.startsWith("text/") || ["application/json", "application/xml", "application/yaml"].includes(mediaType)
133
+ );
134
134
  }
135
135
 
136
136
  function positiveInteger(value) {
package/src/lib/init.js CHANGED
@@ -2,10 +2,12 @@ import { createAutomify } from "./automify.js";
2
2
  import { createBrowserAutomify, withBrowserAutomify } from "./browser-automify.js";
3
3
  import { createCliAutomify } from "./cli-automify.js";
4
4
  import { createDockerCliAutomify } from "./docker-cli-automify.js";
5
+ import { createVirtualCliAutomify } from "./qemu-cli-automify.js";
5
6
  import {
6
7
  createComputerAutomify,
7
8
  createDockerComputerAutomify,
8
- createLocalComputerAutomify
9
+ createLocalComputerAutomify,
10
+ createVirtualComputerAutomify
9
11
  } from "./computer-automify.js";
10
12
  import { createAnthropicModelAdapter } from "./anthropic-model-adapter.js";
11
13
  import { AutomifyError } from "./errors.js";
@@ -57,7 +59,8 @@ export function initAutomify(options = {}) {
57
59
  };
58
60
  const computerDefaults = {
59
61
  ...defaults,
60
- model: options.computerModel ?? provider.computerModel ?? provider.model
62
+ model: options.computerModel ?? provider.computerModel ?? provider.model,
63
+ screenRecording: options.screenRecording ?? options.recording ?? screenshots.recording
61
64
  };
62
65
 
63
66
  return {
@@ -114,8 +117,16 @@ export function initAutomify(options = {}) {
114
117
  });
115
118
  },
116
119
 
120
+ virtualComputer(computerOptions = {}) {
121
+ return createVirtualComputerAutomify({
122
+ ...computerDefaults,
123
+ ...computerOptions,
124
+ client
125
+ });
126
+ },
127
+
117
128
  virtualCli(cliOptions = {}) {
118
- return createDockerCliAutomify({
129
+ return createVirtualCliAutomify({
119
130
  ...defaults,
120
131
  ...cliOptions,
121
132
  client
@@ -29,6 +29,7 @@ const LOCAL_DESKTOP_OPTION_KEYS = new Set([
29
29
  "actionDelayMs",
30
30
  "instructions",
31
31
  "screenshotPath",
32
+ "lockResource",
32
33
  "pixelScale",
33
34
  "mouseScaleX",
34
35
  "mouseScaleY",
@@ -145,7 +146,7 @@ const DEFAULT_DESKTOP_INSTRUCTIONS = [
145
146
 
146
147
  export async function createLocalDesktopComputer(options = {}) {
147
148
  options = normalizeLocalDesktopOptions(options);
148
- const releaseLock = await acquireAdapterLock("local-desktop", {
149
+ const releaseLock = await acquireAdapterLock(options.lockResource ?? "local-desktop", {
149
150
  label: "local desktop adapter"
150
151
  });
151
152
  const setupStartedAt = Date.now();
@@ -41,7 +41,9 @@ export class OpenAIResponsesClient {
41
41
  if (!response.ok) {
42
42
  const message = data?.error?.message ?? data?.message ?? response.statusText;
43
43
  const requestId = response.headers?.get?.("x-request-id") ?? response.headers?.get?.("openai-request-id");
44
- const error = new AutomifyError(`OpenAI Responses request failed${requestId ? ` (${requestId})` : ""}: ${message}`);
44
+ const error = new AutomifyError(
45
+ `OpenAI Responses request failed${requestId ? ` (${requestId})` : ""}: ${message}`
46
+ );
45
47
  error.status = response.status;
46
48
  error.requestId = requestId;
47
49
  if (attempt < this.maxRetries && isRetryableStatus(response.status)) {
@@ -144,13 +146,18 @@ function isRetryableStatus(status) {
144
146
  }
145
147
 
146
148
  function isRetryableError(error) {
147
- return error?.name === "AbortError" || error?.code === "ECONNRESET" || error?.code === "ETIMEDOUT" || /fetch failed/i.test(error?.message ?? "");
149
+ return (
150
+ error?.name === "AbortError" ||
151
+ error?.code === "ECONNRESET" ||
152
+ error?.code === "ETIMEDOUT" ||
153
+ /fetch failed/i.test(error?.message ?? "")
154
+ );
148
155
  }
149
156
 
150
157
  function retryDelay(attempt, baseDelayMs, retryAfter) {
151
158
  const retryAfterMs = Number(retryAfter) * 1000;
152
159
  if (Number.isFinite(retryAfterMs) && retryAfterMs > 0) return retryAfterMs;
153
- return Math.max(0, Number(baseDelayMs) || 0) * (2 ** attempt);
160
+ return Math.max(0, Number(baseDelayMs) || 0) * 2 ** attempt;
154
161
  }
155
162
 
156
163
  function wait(ms) {
@@ -86,7 +86,38 @@ export function applyDockerCliPreset(options = {}) {
86
86
  }
87
87
  }
88
88
 
89
- export const applyVirtualCliPreset = applyDockerCliPreset;
89
+ export function applyVirtualCliPreset(options = {}) {
90
+ switch (options.preset) {
91
+ case undefined:
92
+ case null:
93
+ return options;
94
+ case "repo":
95
+ return mergePreset(
96
+ {
97
+ command: REPO_COMMAND,
98
+ shared: {
99
+ hostPath: process.cwd(),
100
+ containerPath: "/workspace"
101
+ }
102
+ },
103
+ options
104
+ );
105
+ case "locked-down-cli":
106
+ return mergePreset(
107
+ {
108
+ command: {
109
+ approval: "always",
110
+ allow: [],
111
+ block: [/^rm\b/, /^sudo\b/, /^curl\b/, /^wget\b/]
112
+ },
113
+ limits: { steps: 20 }
114
+ },
115
+ options
116
+ );
117
+ default:
118
+ throw unknownPreset("virtual CLI", options.preset, ["repo", "locked-down-cli"]);
119
+ }
120
+ }
90
121
 
91
122
  export function applyDockerDesktopPreset(options = {}) {
92
123
  switch (options.preset) {
@@ -107,7 +138,24 @@ export function applyDockerDesktopPreset(options = {}) {
107
138
  }
108
139
  }
109
140
 
110
- export const applyVirtualDesktopPreset = applyDockerDesktopPreset;
141
+ export function applyVirtualDesktopPreset(options = {}) {
142
+ switch (options.preset) {
143
+ case undefined:
144
+ case null:
145
+ return options;
146
+ case "desktop-review":
147
+ return mergePreset(
148
+ {
149
+ viewport: { width: 1440, height: 900 },
150
+ waitMs: 750,
151
+ screenshotSettleMs: 500
152
+ },
153
+ options
154
+ );
155
+ default:
156
+ throw unknownPreset("virtual desktop", options.preset, ["desktop-review"]);
157
+ }
158
+ }
111
159
 
112
160
  function mergePreset(defaults, options) {
113
161
  return {