jeo-code 0.12.6 → 0.12.7

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/CHANGELOG.md CHANGED
@@ -9,6 +9,17 @@ The README mirrors the latest 5 entries — regenerate with `bun run changelog:s
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.12.7] - 2026-10-18
13
+ _Repair desktop screenshot vision feedback, macOS scrolling, complete drag gestures, and keyboard normalization._
14
+
15
+ ### Fixed
16
+ - **Screenshot vision feedback** — carry PNG attachments through tool execution, batches, and native Anthropic/OpenAI Responses replay instead of spilling base64 as text. Preserve CLI screenshot base64 stdout.
17
+ - **Desktop input** — use Quartz scroll events on macOS (cliclick `w:` waits rather than scrolling), release the mouse after dragging on macOS/Linux, normalize macOS key aliases/modifier combinations, and terminate Linux text options before user text.
18
+ - **Execution reliability** — drain subprocess pipes concurrently with exit, use collision-resistant screenshot paths, reject non-finite numeric inputs and out-of-range waits/scrolls, and explicitly reject unsupported platforms. Existing enablement/supervisor gates remain unchanged.
19
+
20
+ ### Documentation
21
+ - Added [desktop setup and troubleshooting](docs/computer-use.md), including platform requirements, OS permissions, coordinate caveats, and supported action semantics.
22
+
12
23
  ## [0.12.6] - 2026-10-17
13
24
  _Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls._
14
25
 
package/README.ja.md CHANGED
@@ -416,10 +416,10 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
416
416
 
417
417
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
418
418
  - **[Unreleased]**
419
+ - **[0.12.7]** (2026-10-18) — Repair desktop screenshot vision feedback, macOS scrolling, complete drag gestures, and keyboard normalization.
419
420
  - **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
420
421
  - **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
421
422
  - **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
422
- - **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
423
423
 
424
424
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
425
425
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -417,10 +417,10 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
417
417
 
418
418
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
419
419
  - **[Unreleased]**
420
+ - **[0.12.7]** (2026-10-18) — Repair desktop screenshot vision feedback, macOS scrolling, complete drag gestures, and keyboard normalization.
420
421
  - **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
421
422
  - **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
422
423
  - **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
423
- - **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
424
424
 
425
425
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
426
426
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -409,10 +409,10 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
409
409
 
410
410
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
411
411
  - **[Unreleased]**
412
+ - **[0.12.7]** (2026-10-18) — Repair desktop screenshot vision feedback, macOS scrolling, complete drag gestures, and keyboard normalization.
412
413
  - **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
413
414
  - **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
414
415
  - **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
415
- - **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
416
416
 
417
417
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
418
418
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -414,10 +414,10 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
414
414
 
415
415
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
416
416
  - **[Unreleased]**
417
+ - **[0.12.7]** (2026-10-18) — Repair desktop screenshot vision feedback, macOS scrolling, complete drag gestures, and keyboard normalization.
417
418
  - **[0.12.6]** (2026-10-17) — Preserve OpenAI subscription authentication behind configured proxies, isolate account catalogs, and give provider-specific recovery instructions while retaining Antigravity OAuth calls.
418
419
  - **[0.12.5]** (2026-10-17) — Antigravity and Kimi provider support in interactive setup, Kimi live model cache rehydration, immediate discovery caching on provider probing.
419
420
  - **[0.12.4]** (2026-10-17) — Refresh Antigravity provider (`antigravity/*`) account catalogs with `antigravity/hub/2.23.1` so entitled Gemini 4 Argon and Gemini 3.8 Flash tiers appear directly in `/model provider antigravity`.
420
- - **[0.12.3]** (2026-10-17) — Anthropic static model catalog trimmed to claude-5.x with live API discovery, and `/route off` strictly fixes the active model with no failover.
421
421
 
422
422
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
423
423
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.12.6",
3
+ "version": "0.12.7",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -1215,6 +1215,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1215
1215
  const { tool, arguments: args } = call;
1216
1216
  let success: boolean;
1217
1217
  let output: string;
1218
+ let images: import("../ai/types").ImageAttachment[] | undefined;
1218
1219
 
1219
1220
  if (tool === "done") {
1220
1221
  success = false;
@@ -1242,6 +1243,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1242
1243
  const onProgress = ev.onToolProgress ? (partial: string) => ev.onToolProgress!(tool, partial) : undefined;
1243
1244
  const res = await handler(args ?? {}, cwd, onProgress);
1244
1245
  success = res.success;
1246
+ images = res.images;
1245
1247
  output = res.success ? res.output : (res.error ? (res.output ? `${res.error}\n${res.output}` : res.error) : res.output);
1246
1248
  } catch (err: any) {
1247
1249
  success = false;
@@ -1250,7 +1252,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1250
1252
  }
1251
1253
  }
1252
1254
  }
1253
- return { success, output };
1255
+ return { success, output, images };
1254
1256
  };
1255
1257
 
1256
1258
  const WRITE_TOOLS = new Set(["write", "edit"]);
@@ -1293,7 +1295,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1293
1295
  }
1294
1296
  }
1295
1297
 
1296
- const results: { success: boolean; output: string; executed: boolean }[] = Array.from(
1298
+ const results: { success: boolean; output: string; executed: boolean; images?: import("../ai/types").ImageAttachment[] }[] = Array.from(
1297
1299
  { length: toolCalls.length },
1298
1300
  () => ({ success: false, output: "", executed: false })
1299
1301
  );
@@ -1385,6 +1387,8 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
1385
1387
  }));
1386
1388
  pushAssistantTurn(history, responseText, reasonBuf, artifactBuf, toolUse);
1387
1389
  const resultMsg: Message = { role: "user", content: resultBlocks.join("\n\n"), toolResults };
1390
+ const images = indices.flatMap(idx => results[idx].images ?? []);
1391
+ if (images.length) resultMsg.images = images;
1388
1392
  if (hookExtra) resultMsg.toolResultExtra = hookExtra;
1389
1393
  history.push(resultMsg);
1390
1394
  };
@@ -22,6 +22,7 @@ async function readMutationLock(cwd: string): Promise<WorkflowState | null> {
22
22
  }
23
23
 
24
24
  export interface ToolResult {
25
+ images?: import("../ai/types").ImageAttachment[];
25
26
  success: boolean;
26
27
  output: string;
27
28
  error?: string;
@@ -240,6 +240,7 @@ export function buildAnthropicMessages(messages: Message[], model: string, think
240
240
  const blocks: AnthropicContentBlock[] = m.toolResults.map(tr => ({
241
241
  type: "tool_result", tool_use_id: tr.id, content: tr.output, is_error: tr.isError,
242
242
  }));
243
+ for (const img of m.images ?? []) blocks.push({ type: "image", source: { type: "base64", media_type: img.mediaType, data: img.data } });
243
244
  if (m.toolResultExtra) blocks.push({ type: "text", text: m.toolResultExtra });
244
245
  return { role: "user", content: blocks };
245
246
  }
@@ -87,6 +87,7 @@ export function buildResponsesInput(messages: Message[], modelKey: string, strip
87
87
  && nonSystem[i - 1].role === "assistant" && responsesNativizable(nonSystem[i - 1], modelKey)) {
88
88
  for (const tr of m.toolResults) items.push({ type: "function_call_output", call_id: tr.id, output: tr.output });
89
89
  if (m.toolResultExtra) items.push({ role: "user", content: [{ type: "input_text", text: m.toolResultExtra }] });
90
+ if (m.images?.length) items.push({ role: "user", content: m.images.map(img => ({ type: "input_image", image_url: `data:${img.mediaType};base64,${img.data}` })) });
90
91
  return;
91
92
  }
92
93
  items.push(plain(m));
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import * as os from "node:os";
4
4
  import { computerSupervisor } from "../agent/computer-supervisor";
5
+ import type { ImageAttachment } from "../ai/types";
5
6
  import { readGlobalConfig } from "../agent/state";
6
7
 
7
8
  export interface ComputerAction {
@@ -36,9 +37,11 @@ async function logAudit(action: ComputerAction, success: boolean, error?: string
36
37
  async function runCommand(cmd: string[]): Promise<{ success: boolean; stdout: string; stderr: string }> {
37
38
  try {
38
39
  const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
39
- await proc.exited;
40
- const stdout = await new Response(proc.stdout).text();
41
- const stderr = await new Response(proc.stderr).text();
40
+ const [stdout, stderr] = await Promise.all([
41
+ new Response(proc.stdout).text(),
42
+ new Response(proc.stderr).text(),
43
+ proc.exited,
44
+ ]);
42
45
  return {
43
46
  success: proc.exitCode === 0,
44
47
  stdout,
@@ -53,7 +56,7 @@ async function runCommand(cmd: string[]): Promise<{ success: boolean; stdout: st
53
56
  }
54
57
  }
55
58
 
56
- export async function executeComputerAction(action: ComputerAction, opts?: { enabledOverride?: boolean }): Promise<{ success: boolean; output: string; error?: string }> {
59
+ export async function executeComputerAction(action: ComputerAction, opts?: { enabledOverride?: boolean }): Promise<{ success: boolean; output: string; error?: string; images?: ImageAttachment[] }> {
57
60
  let enabled = opts?.enabledOverride;
58
61
  if (enabled === undefined) {
59
62
  const config = await readGlobalConfig().catch(() => null);
@@ -68,6 +71,18 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
68
71
  }
69
72
 
70
73
 
74
+ if (!action || typeof action !== "object") return { success: false, output: "", error: "Expected a computer action object." };
75
+ for (const field of ["x", "y", "deltaX", "deltaY", "duration"] as const) {
76
+ if (action[field] !== undefined && (typeof action[field] !== "number" || !Number.isFinite(action[field]))) {
77
+ return { success: false, output: "", error: `Invalid finite number for ${field}.` };
78
+ }
79
+ }
80
+ if (action.duration !== undefined && (action.duration < 0 || action.duration > 60)) {
81
+ return { success: false, output: "", error: "duration must be between 0 and 60 seconds." };
82
+ }
83
+ if (os.platform() !== "darwin" && os.platform() !== "linux") {
84
+ return { success: false, output: "", error: "Computer use supports macOS and Linux/X11 only." };
85
+ }
71
86
  // Read-only actions don't need supervisor inputAllowed check
72
87
  const isReadOnly = action.action === "screenshot" || action.action === "wait";
73
88
  if (!isReadOnly) {
@@ -83,7 +98,7 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
83
98
  switch (action.action) {
84
99
  case "screenshot": {
85
100
  const tmpDir = os.tmpdir();
86
- const screenPath = path.join(tmpDir, `jeo-screen-${Date.now()}.png`);
101
+ const screenPath = path.join(tmpDir, `jeo-screen-${crypto.randomUUID()}.png`);
87
102
  let cmd: string[];
88
103
  if (isMac) {
89
104
  cmd = ["screencapture", "-x", screenPath];
@@ -100,7 +115,7 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
100
115
  const base64 = bytes.toString("base64");
101
116
  await fs.unlink(screenPath).catch(() => {});
102
117
  await logAudit(action, true);
103
- return { success: true, output: base64 };
118
+ return { success: true, output: "Screenshot captured.", images: [{ mediaType: "image/png", data: base64 }] };
104
119
  } catch (err: any) {
105
120
  await logAudit(action, false, err.message);
106
121
  return { success: false, output: "", error: `Failed to read screenshot file: ${err.message}` };
@@ -164,9 +179,9 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
164
179
  }
165
180
  let cmd: string[];
166
181
  if (isMac) {
167
- cmd = ["cliclick", `dd:${action.x},${action.y}`];
182
+ cmd = ["cliclick", "dd:.", `dm:${action.x},${action.y}`, `du:${action.x},${action.y}`];
168
183
  } else {
169
- cmd = ["xdotool", "mousemove", String(action.x), String(action.y), "mousedown", "1"];
184
+ cmd = ["xdotool", "mousedown", "1", "mousemove", String(action.x), String(action.y), "mouseup", "1"];
170
185
  }
171
186
  const res = await runCommand(cmd);
172
187
  await logAudit(action, res.success, res.success ? undefined : res.stderr);
@@ -179,9 +194,14 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
179
194
  let cmd: string[];
180
195
  const dx = action.deltaX ?? 0;
181
196
  const dy = action.deltaY ?? 0;
197
+ if (!Number.isInteger(dx) || !Number.isInteger(dy) || Math.abs(dx) > 1000 || Math.abs(dy) > 1000) {
198
+ return { success: false, output: "", error: "Scroll deltas must be integers between -1000 and 1000." };
199
+ }
200
+ if (dx === 0 && dy === 0) return { success: true, output: "Scroll skipped (zero deltas)." };
182
201
  if (isMac) {
183
- // cliclick scroll deltaY deltaX
184
- cmd = ["cliclick", `w:${dy},${dx}`];
202
+ // cliclick's w: command WAITS; it cannot scroll. Use Quartz line events.
203
+ cmd = ["/usr/bin/osascript", "-l", "JavaScript", "-e",
204
+ `ObjC.import('Cocoa'); var event = $.CGEventCreateScrollWheelEvent(null, 1, 2, ${dy}, ${dx}); $.CGEventPost(0, event);`];
185
205
  } else {
186
206
  // xdotool click 4 (scroll up), 5 (scroll down), 6 (scroll left), 7 (scroll right)
187
207
  const actions: string[] = [];
@@ -212,7 +232,7 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
212
232
  if (isMac) {
213
233
  cmd = ["cliclick", `t:${action.text}`];
214
234
  } else {
215
- cmd = ["xdotool", "type", action.text];
235
+ cmd = ["xdotool", "type", "--clearmodifiers", "--", action.text];
216
236
  }
217
237
  const res = await runCommand(cmd);
218
238
  await logAudit(action, res.success, res.success ? undefined : res.stderr);
@@ -227,7 +247,16 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
227
247
  }
228
248
  let cmd: string[];
229
249
  if (isMac) {
230
- cmd = ["cliclick", `kp:${action.key}`];
250
+ const aliases: Record<string, string> = { return: "return", enter: "return", escape: "esc", backspace: "delete", arrowup: "arrow-up", arrowdown: "arrow-down", arrowleft: "arrow-left", arrowright: "arrow-right", pageup: "page-up", pagedown: "page-down" };
251
+ const parts = action.key.toLowerCase().split("+");
252
+ const key = parts.pop()!;
253
+ const modifiers = parts.map(p => ({ command: "cmd", meta: "cmd", control: "ctrl", option: "alt" }[p] ?? p));
254
+ if (modifiers.some(p => !["cmd", "ctrl", "alt", "shift", "fn"].includes(p))) {
255
+ return { success: false, output: "", error: "Unknown key modifier." };
256
+ }
257
+ const press = key.length === 1 ? `t:${key}` : `kp:${aliases[key] ?? key}`;
258
+ cmd = ["cliclick", ...(modifiers.length ? [`kd:${modifiers.join(",")}`] : []), press,
259
+ ...(modifiers.length ? [`ku:${modifiers.join(",")}`] : [])];
231
260
  } else {
232
261
  cmd = ["xdotool", "key", action.key];
233
262
  }
@@ -250,6 +279,7 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
250
279
  return { success: false, output: "", error: "Missing actions array for batch action." };
251
280
  }
252
281
  const results: string[] = [];
282
+ const images: ImageAttachment[] = [];
253
283
  for (let i = 0; i < action.actions.length; i++) {
254
284
  const subAction = action.actions[i];
255
285
  const res = await executeComputerAction(subAction, opts);
@@ -257,12 +287,13 @@ export async function executeComputerAction(action: ComputerAction, opts?: { ena
257
287
  if (!res.success) {
258
288
  const err = `Batch failed at step ${i}: ${res.error}`;
259
289
  await logAudit(action, false, err);
260
- return { success: false, output: results.join("\n"), error: err };
290
+ return { success: false, output: results.join("\n"), error: err, images };
261
291
  }
262
292
  results.push(`Step ${i}: ${res.output}`);
293
+ images.push(...(res.images ?? []));
263
294
  }
264
295
  await logAudit(action, true);
265
- return { success: true, output: results.join("\n") };
296
+ return { success: true, output: results.join("\n"), images };
266
297
  }
267
298
 
268
299
  default:
@@ -329,7 +360,7 @@ export async function runComputerCommand(args: string[]): Promise<void> {
329
360
  const res = await executeComputerAction(action);
330
361
  if (res.success) {
331
362
  if (actionName === "screenshot") {
332
- console.log(res.output); // base64 output
363
+ console.log(res.images?.[0]?.data ?? res.output); // preserve CLI base64 output
333
364
  } else {
334
365
  console.log(res.output);
335
366
  }