You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/10/09 12:50:13 UTC
(maka) branch main updated: feat(computer-use): show snapshot selected text in model observations (#5050)
This is an automated email from the ASF dual-hosted git repository.
Astro-Han pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/main by this push:
new 6e1f1d94f7 feat(computer-use): show snapshot selected text in model observations (#5050)
6e1f1d94f7 is described below
commit 6e1f1d94f71e48d2618e06b7d9aa2cd12fd44a00
Author: 梧桐雨 <14...@users.noreply.github.com>
AuthorDate: Fri Oct 9 20:50:08 2026 +0800
feat(computer-use): show snapshot selected text in model observations (#5050)
select_text already returns a snapshot.selectedText payload, but the
shared observation type and renderer dropped it, so the model could not
confirm what was selected. Surface the field on CuObservation and write
it in the observation header, truncated like value and withheld when the
focused control is a secure field.
Generated-by: pi-coding-agent
test(runtime): cover selectedText length cap in observation header
The renderer already truncated like value. Review required a test that
fails if a 300-character selection is shown in full.
Generated-by: pi-coding-agent
---
packages/computer-use/src/maka-cu-backend.ts | 12 ++---
packages/core/src/computer-use.ts | 12 +++++
.../computer-use-observation-text.test.ts | 51 ++++++++++++++++++++++
.../runtime/src/computer-use-observation-text.ts | 21 +++++++++
packages/runtime/src/computer-use-types.ts | 9 ++++
5 files changed, 99 insertions(+), 6 deletions(-)
diff --git a/packages/computer-use/src/maka-cu-backend.ts b/packages/computer-use/src/maka-cu-backend.ts
index ea2cdfcd3b..9d77ba9824 100644
--- a/packages/computer-use/src/maka-cu-backend.ts
+++ b/packages/computer-use/src/maka-cu-backend.ts
@@ -33,12 +33,12 @@
// not exist as a signed artifact yet, so nothing may fall back to it silently.
//
// What the protocol declares and Maka's own types cannot yet carry: per-element
-// `truncated`, `actions`, `placeholder`, `focused` and per-snapshot
-// `selectedText`. They are read and validated here — a missing declared field is
-// version skew the host must catch — but only the truncation flags reach
-// anywhere, through `onTrace`. Giving them a model-facing home means new fields
-// on `CuObservedElement`/`CuObservation`, which this change deliberately does
-// not make.
+// `truncated`, `actions`, `placeholder` and `focused`. They are read and
+// validated here — a missing declared field is version skew the host must catch
+// — but only the truncation flags reach anywhere, through `onTrace`. Giving
+// them a model-facing home means new fields on `CuObservedElement`, which this
+// file does not add. Snapshot `selectedText` is on `CuObservation` and is
+// rendered for the model.
import { randomUUID } from 'node:crypto';
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
diff --git a/packages/core/src/computer-use.ts b/packages/core/src/computer-use.ts
index bd3cc2d2e3..12c308d318 100644
--- a/packages/core/src/computer-use.ts
+++ b/packages/core/src/computer-use.ts
@@ -101,6 +101,18 @@ export interface ComputerUseRect {
height: number;
}
+/**
+ * Text currently selected in a Computer Use target window.
+ *
+ * The executor reports this per snapshot (`maka.cu/2` §5.2). `text` is the
+ * selected string; `truncated` is true when the executor cut it. Rendering
+ * for a model must still apply its own length cap — the wire does not.
+ */
+export interface ComputerUseSelectedText {
+ text: string;
+ truncated: boolean;
+}
+
export interface ComputerUseFrameIdentity {
frameId: string;
epoch: number;
diff --git a/packages/runtime/src/__tests__/computer-use-observation-text.test.ts b/packages/runtime/src/__tests__/computer-use-observation-text.test.ts
index dab96bc1a2..9eea540ca0 100644
--- a/packages/runtime/src/__tests__/computer-use-observation-text.test.ts
+++ b/packages/runtime/src/__tests__/computer-use-observation-text.test.ts
@@ -82,6 +82,57 @@ test('an element that is its own parent is a root, not a hang', () => {
assert.deepEqual(lines(text).slice(1), ['5 AXGroup']);
});
+test('window selected text is written in the header so the model can read it', () => {
+ const text = renderObservationForModel({
+ ...observation([{ elementId: '0', role: 'AXTextField', label: 'Body', value: 'hello' }]),
+ selectedText: { text: 'hello world', truncated: false },
+ });
+ assert.match(lines(text)[0] ?? '', /selected_text="hello world"/);
+ assert.doesNotMatch(lines(text)[0] ?? '', /truncated=true/);
+});
+
+test('executor-truncated selected text keeps the truncated flag', () => {
+ const text = renderObservationForModel({
+ ...observation([{ elementId: '0', role: 'AXTextField' }]),
+ selectedText: { text: 'partial', truncated: true },
+ });
+ assert.match(lines(text)[0] ?? '', /selected_text="partial"\(truncated=true\)/);
+});
+
+test('selected text in a focused secure field is withheld', () => {
+ const text = renderObservationForModel({
+ ...observation([
+ {
+ elementId: '0',
+ role: 'AXTextField',
+ subrole: 'AXSecureTextField',
+ focused: true,
+ },
+ ]),
+ selectedText: { text: 'hunter2', truncated: false },
+ });
+ assert.match(lines(text)[0] ?? '', /selected_text=withheld\(secure_field\)/);
+ assert.doesNotMatch(lines(text)[0] ?? '', /hunter2/);
+});
+
+test('an oversized selected text is shortened visibly, not silently', () => {
+ const text = renderObservationForModel({
+ ...observation([{ elementId: '0', role: 'AXTextField' }]),
+ selectedText: { text: 'x'.repeat(300), truncated: false },
+ });
+ const header = lines(text)[0] ?? '';
+ assert.match(header, /selected_text=/);
+ assert.match(header, /x{256}\u2026\(\+44 chars\)/);
+ assert.doesNotMatch(header, /x{257}/);
+});
+
+test('an observation without selected text does not mention it', () => {
+ const text = renderObservationForModel(
+ observation([{ elementId: '0', role: 'AXButton', label: 'OK' }]),
+ );
+ assert.doesNotMatch(lines(text)[0] ?? '', /selected_text/);
+});
+
test('a post-action no-change observation says so without repeating the tree', () => {
const text = renderObservationForModel({
...observation([{ elementId: '0', role: 'AXWindow', label: 'Main' }]),
diff --git a/packages/runtime/src/computer-use-observation-text.ts b/packages/runtime/src/computer-use-observation-text.ts
index fe09216438..78df6706a3 100644
--- a/packages/runtime/src/computer-use-observation-text.ts
+++ b/packages/runtime/src/computer-use-observation-text.ts
@@ -481,9 +481,30 @@ function header(
'truncated=true(the tree was cut short; an element you expect may exist but not be listed)',
);
}
+ const selectedText = selectedTextCaption(observation);
+ if (selectedText) parts.push(selectedText);
return parts.join(' ');
}
+/**
+ * Window selection is application text. A password field that currently holds
+ * keys must not leak that text through this caption; otherwise truncate like
+ * `value` and keep the executor's own truncated flag.
+ */
+function selectedTextCaption(observation: CuObservation): string | undefined {
+ const selected = observation.selectedText;
+ if (!selected) return undefined;
+ const focused = observation.elements.find((element) => element.focused === true);
+ const role = `${focused?.role ?? ''} ${focused?.subrole ?? ''}`;
+ if (/secure/i.test(role)) {
+ return 'selected_text=withheld(secure_field)';
+ }
+ const shown = truncate(selected.text);
+ return selected.truncated === true
+ ? `selected_text=${quote(shown)}(truncated=true)`
+ : `selected_text=${quote(shown)}`;
+}
+
/**
* A subrole earns its place when it is telling the model something the rest of
* the line does not.
diff --git a/packages/runtime/src/computer-use-types.ts b/packages/runtime/src/computer-use-types.ts
index 288b9a8a3c..b5839de9a7 100644
--- a/packages/runtime/src/computer-use-types.ts
+++ b/packages/runtime/src/computer-use-types.ts
@@ -23,6 +23,7 @@ import type {
ComputerUseEffect,
ComputerUseErrorCode,
ComputerUsePageIdentity,
+ ComputerUseSelectedText,
CuAction,
CuPoint,
} from '@maka/core/computer-use';
@@ -234,6 +235,14 @@ export interface CuObservation {
* route. This says the list is a prefix, not an inventory.
*/
truncated?: boolean;
+ /**
+ * Text currently selected in the target window, from the executor snapshot.
+ *
+ * `select_text` names a range; this is the only account of what came out of
+ * it. Absent when the executor reported none. Model rendering must truncate
+ * and must not persist this into the call record.
+ */
+ selectedText?: ComputerUseSelectedText;
elements: CuObservedElement[];
screenshot?: CuScreenshot;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org