@forsion/tangu-computer-use 0.5.8

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/LICENSE +26 -0
  3. package/LICENSE.upstream +21 -0
  4. package/README.md +169 -0
  5. package/UPSTREAM.md +277 -0
  6. package/check.mjs +369 -0
  7. package/icon.png +0 -0
  8. package/install.sh +112 -0
  9. package/main.js +541 -0
  10. package/manifest.json +47 -0
  11. package/native/linux/bridge-rs/Cargo.lock +1204 -0
  12. package/native/linux/bridge-rs/Cargo.toml +18 -0
  13. package/native/linux/bridge-rs/src/atspi.rs +640 -0
  14. package/native/linux/bridge-rs/src/error.rs +65 -0
  15. package/native/linux/bridge-rs/src/lib.rs +11 -0
  16. package/native/linux/bridge-rs/src/main.rs +1074 -0
  17. package/native/linux/bridge-rs/src/protocol.rs +49 -0
  18. package/native/linux/bridge-rs/src/state.rs +293 -0
  19. package/native/linux/bridge-rs/src/wayland.rs +89 -0
  20. package/native/linux/bridge-rs/src/x11.rs +909 -0
  21. package/native/linux/bridge-rs/tests/protocol_tests.rs +73 -0
  22. package/native/macos/agent_cursor.swift +184 -0
  23. package/native/macos/agent_cursor_motion.swift +252 -0
  24. package/native/macos/agent_cursor_tests.swift +128 -0
  25. package/native/macos/agent_highlight.swift +229 -0
  26. package/native/macos/agent_highlight_tests.swift +99 -0
  27. package/native/macos/bridge.swift +3852 -0
  28. package/native/macos/foreground_activity.swift +57 -0
  29. package/native/macos/foreground_activity_tests.swift +31 -0
  30. package/native/macos/live_stream.swift +289 -0
  31. package/native/windows/bridge-rs/Cargo.lock +396 -0
  32. package/native/windows/bridge-rs/Cargo.toml +30 -0
  33. package/native/windows/bridge-rs/src/capture.rs +518 -0
  34. package/native/windows/bridge-rs/src/error.rs +81 -0
  35. package/native/windows/bridge-rs/src/input.rs +501 -0
  36. package/native/windows/bridge-rs/src/lib.rs +16 -0
  37. package/native/windows/bridge-rs/src/main.rs +1683 -0
  38. package/native/windows/bridge-rs/src/protocol.rs +53 -0
  39. package/native/windows/bridge-rs/src/refs.rs +237 -0
  40. package/native/windows/bridge-rs/src/state.rs +55 -0
  41. package/native/windows/bridge-rs/src/uia.rs +1252 -0
  42. package/native/windows/bridge-rs/src/window.rs +718 -0
  43. package/native/windows/bridge-rs/tests/protocol_tests.rs +252 -0
  44. package/native/windows/bridge-rs/tests/refs_tests.rs +86 -0
  45. package/native/windows/bridge-rs/tests/state_tests.rs +33 -0
  46. package/package.json +74 -0
  47. package/prebuilt/linux/arm64/linux-bridge +0 -0
  48. package/prebuilt/linux/x64/linux-bridge +0 -0
  49. package/prebuilt/macos/arm64/bridge +0 -0
  50. package/prebuilt/macos/arm64/tangu-computer-use.app.json +10 -0
  51. package/prebuilt/macos/arm64/tangu-computer-use.app.zip +0 -0
  52. package/prebuilt/macos/x64/bridge +0 -0
  53. package/prebuilt/macos/x64/tangu-computer-use.app.json +10 -0
  54. package/prebuilt/macos/x64/tangu-computer-use.app.zip +0 -0
  55. package/prebuilt/windows/windows-bridge.exe +0 -0
  56. package/scripts/blind-click.check.mjs +128 -0
  57. package/scripts/build-native.mjs +302 -0
  58. package/scripts/build.mjs +84 -0
  59. package/scripts/calc-fixture.mjs +78 -0
  60. package/scripts/cli-exit.check.mjs +49 -0
  61. package/scripts/helper-path.check.mjs +82 -0
  62. package/scripts/helper-refresh.check.mjs +62 -0
  63. package/scripts/helper-signal.check.mjs +59 -0
  64. package/scripts/highlight.check.mjs +32 -0
  65. package/scripts/keychain-free-install.check.mjs +140 -0
  66. package/scripts/live-view.check.mjs +124 -0
  67. package/scripts/macos-bundle.d.mts +1 -0
  68. package/scripts/macos-bundle.mjs +119 -0
  69. package/scripts/make-signing-cert.sh +57 -0
  70. package/scripts/mini-foreground.check.mjs +11 -0
  71. package/scripts/no-foreground.check.mjs +81 -0
  72. package/scripts/overlay-visible.check.mjs +221 -0
  73. package/scripts/package-macos-app.mjs +39 -0
  74. package/scripts/permissions.check.mjs +199 -0
  75. package/scripts/platform-contract.check.mjs +30 -0
  76. package/scripts/setup-helper.mjs +154 -0
  77. package/scripts/tangu-computer-use.entitlements +10 -0
  78. package/scripts/verify-macos-bundles.mjs +25 -0
  79. package/scripts/verify-package.mjs +74 -0
  80. package/skills/computer-use/SKILL.md +110 -0
  81. package/src/foregroundNote.ts +43 -0
  82. package/src/helperState.ts +31 -0
  83. package/src/index.ts +63 -0
  84. package/src/onboarding.ts +198 -0
  85. package/src/pi-compat.ts +49 -0
  86. package/src/settings.ts +11 -0
  87. package/src/setup.ts +97 -0
  88. package/src/tools.ts +289 -0
  89. package/src/vendor/actions.ts +130 -0
  90. package/src/vendor/bridge.ts +2405 -0
  91. package/src/vendor/cdp.ts +658 -0
  92. package/src/vendor/config.ts +113 -0
  93. package/src/vendor/contract.ts +104 -0
  94. package/src/vendor/note.ts +195 -0
  95. package/src/vendor/outline.ts +651 -0
  96. package/src/vendor/output.ts +134 -0
  97. package/src/vendor/permissions.ts +111 -0
  98. package/src/vendor/platform/architecture.ts +23 -0
  99. package/src/vendor/platform/coerce.ts +16 -0
  100. package/src/vendor/platform/index.ts +59 -0
  101. package/src/vendor/platform/linux/backend.ts +186 -0
  102. package/src/vendor/platform/linux/helper.ts +238 -0
  103. package/src/vendor/platform/macos/backend.ts +131 -0
  104. package/src/vendor/platform/macos/browser.ts +110 -0
  105. package/src/vendor/platform/macos/helper-path.d.mts +9 -0
  106. package/src/vendor/platform/macos/helper-path.mjs +34 -0
  107. package/src/vendor/platform/macos/helper.ts +291 -0
  108. package/src/vendor/platform/macos/permissions.ts +146 -0
  109. package/src/vendor/platform/types.ts +222 -0
  110. package/src/vendor/platform/windows/backend.ts +142 -0
  111. package/src/vendor/platform/windows/helper.ts +140 -0
  112. package/src/vendor/root-selection.ts +25 -0
  113. package/src/vendor/runtime.ts +129 -0
  114. package/src/vendor/state.ts +146 -0
  115. package/src/vendor/view.ts +147 -0
  116. package/tangu-plugins/computer-use/dist/foregroundNote.js +19 -0
  117. package/tangu-plugins/computer-use/dist/index.js +5672 -0
  118. package/tangu-plugins/computer-use/tangu-plugin.json +9 -0
  119. package/tsconfig.json +22 -0
  120. package/types/tangu-agent.d.ts +142 -0
@@ -0,0 +1,49 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use serde_json::Value;
3
+
4
+ use crate::ProtocolError;
5
+
6
+ pub const PROTOCOL_VERSION: u32 = 4;
7
+
8
+ #[derive(Debug, Clone, Deserialize)]
9
+ pub struct Request {
10
+ #[serde(rename = "protocolVersion")]
11
+ pub protocol_version: u32,
12
+ pub id: String,
13
+ pub cmd: String,
14
+ pub args: Value,
15
+ }
16
+
17
+ #[derive(Debug, Clone, Serialize)]
18
+ pub struct Response {
19
+ #[serde(rename = "protocolVersion")]
20
+ pub protocol_version: u32,
21
+ pub id: String,
22
+ pub ok: bool,
23
+ #[serde(skip_serializing_if = "Option::is_none")]
24
+ pub result: Option<Value>,
25
+ #[serde(skip_serializing_if = "Option::is_none")]
26
+ pub error: Option<ProtocolError>,
27
+ }
28
+
29
+ impl Response {
30
+ pub fn ok(id: &str, result: Value) -> Self {
31
+ Self {
32
+ protocol_version: PROTOCOL_VERSION,
33
+ id: id.to_owned(),
34
+ ok: true,
35
+ result: Some(result),
36
+ error: None,
37
+ }
38
+ }
39
+
40
+ pub fn err(id: &str, error: ProtocolError) -> Self {
41
+ Self {
42
+ protocol_version: PROTOCOL_VERSION,
43
+ id: id.to_owned(),
44
+ ok: false,
45
+ result: None,
46
+ error: Some(error),
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,293 @@
1
+ use std::collections::{HashMap, VecDeque};
2
+ use std::sync::atomic::{AtomicU64, Ordering};
3
+
4
+ use crate::atspi::{AccessibleRef, NodeSnapshot, RootSnapshot};
5
+ use crate::{ErrorCode, ProtocolError};
6
+
7
+ pub const MAX_LOOK_RECORDS: usize = 8;
8
+
9
+ #[derive(Debug, Clone)]
10
+ pub struct LookRecord {
11
+ pub root: RootSnapshot,
12
+ pub nodes: HashMap<String, NodeSnapshot>,
13
+ pub image_width: u16,
14
+ pub image_height: u16,
15
+ }
16
+
17
+ #[derive(Debug, Default)]
18
+ pub struct HelperState {
19
+ roots: HashMap<String, RootSnapshot>,
20
+ root_refs: HashMap<AccessibleRef, String>,
21
+ looks: HashMap<String, LookRecord>,
22
+ look_order: VecDeque<String>,
23
+ next_root: u64,
24
+ next_look: u64,
25
+ }
26
+
27
+ impl HelperState {
28
+ pub fn replace_roots(&mut self, roots: Vec<RootSnapshot>) -> Vec<(String, RootSnapshot)> {
29
+ let mut next = HashMap::new();
30
+ let mut result = Vec::with_capacity(roots.len());
31
+ for root in roots {
32
+ let reference = self
33
+ .root_refs
34
+ .get(&root.accessible)
35
+ .cloned()
36
+ .unwrap_or_else(|| {
37
+ self.next_root += 1;
38
+ format!("@w{}", self.next_root)
39
+ });
40
+ self.root_refs
41
+ .insert(root.accessible.clone(), reference.clone());
42
+ next.insert(reference.clone(), root.clone());
43
+ result.push((reference, root));
44
+ }
45
+ self.roots = next;
46
+ self.root_refs
47
+ .retain(|_, reference| self.roots.contains_key(reference));
48
+ result
49
+ }
50
+
51
+ pub fn root(&self, reference: &str) -> Option<RootSnapshot> {
52
+ self.roots.get(reference).cloned()
53
+ }
54
+
55
+ pub fn roots(&self) -> Vec<(String, RootSnapshot)> {
56
+ let mut roots = self
57
+ .roots
58
+ .iter()
59
+ .map(|(reference, root)| (reference.clone(), root.clone()))
60
+ .collect::<Vec<_>>();
61
+ roots.sort_by(|a, b| a.0.cmp(&b.0));
62
+ roots
63
+ }
64
+
65
+ pub fn insert_look(
66
+ &mut self,
67
+ root: RootSnapshot,
68
+ raw_nodes: Vec<NodeSnapshot>,
69
+ base: Option<&LookRecord>,
70
+ ) -> (String, Vec<NodeSnapshot>) {
71
+ self.next_look += 1;
72
+ let look_id = format!("look_{}", self.next_look);
73
+ let mut nodes = base.map(|record| record.nodes.clone()).unwrap_or_default();
74
+ let existing_refs = base
75
+ .map(|record| {
76
+ record
77
+ .nodes
78
+ .values()
79
+ .map(|node| (node.accessible.clone(), node.reference.clone()))
80
+ .collect::<HashMap<_, _>>()
81
+ })
82
+ .unwrap_or_default();
83
+ let mut output = Vec::with_capacity(raw_nodes.len());
84
+ for (index, mut node) in raw_nodes.into_iter().enumerate() {
85
+ node.reference = existing_refs
86
+ .get(&node.accessible)
87
+ .cloned()
88
+ .unwrap_or_else(|| format!("linux:{look_id}:@e{}", index + 1));
89
+ nodes.insert(node.reference.clone(), node.clone());
90
+ output.push(node);
91
+ }
92
+ let (image_width, image_height) = base
93
+ .map(|record| (record.image_width, record.image_height))
94
+ .or_else(|| {
95
+ root.frame.as_ref().map(|frame| {
96
+ (
97
+ frame.width.max(1).min(i32::from(u16::MAX)) as u16,
98
+ frame.height.max(1).min(i32::from(u16::MAX)) as u16,
99
+ )
100
+ })
101
+ })
102
+ .unwrap_or((1, 1));
103
+ self.looks.insert(
104
+ look_id.clone(),
105
+ LookRecord {
106
+ root,
107
+ nodes,
108
+ image_width,
109
+ image_height,
110
+ },
111
+ );
112
+ self.look_order.push_back(look_id.clone());
113
+ while self.look_order.len() > MAX_LOOK_RECORDS {
114
+ if let Some(expired) = self.look_order.pop_front() {
115
+ self.looks.remove(&expired);
116
+ }
117
+ }
118
+ (look_id, output)
119
+ }
120
+
121
+ pub fn set_look_image_size(
122
+ &mut self,
123
+ look_id: &str,
124
+ width: u16,
125
+ height: u16,
126
+ ) -> Result<(), ProtocolError> {
127
+ let record = self.looks.get_mut(look_id).ok_or_else(|| {
128
+ ProtocolError::new("Owning look is no longer available", ErrorCode::StaleLook)
129
+ })?;
130
+ record.image_width = width.max(1);
131
+ record.image_height = height.max(1);
132
+ Ok(())
133
+ }
134
+
135
+ pub fn scope(
136
+ &self,
137
+ reference: &str,
138
+ base_look_id: Option<&str>,
139
+ ) -> Result<(LookRecord, NodeSnapshot), ProtocolError> {
140
+ if let Some(look_id) = base_look_id {
141
+ let record = self.look(look_id)?;
142
+ let node = record.nodes.get(reference).cloned().ok_or_else(|| {
143
+ ProtocolError::new("Scope ref is not owned by baseLookId", ErrorCode::StaleRef)
144
+ })?;
145
+ return Ok((record, node));
146
+ }
147
+ self.look_order
148
+ .iter()
149
+ .rev()
150
+ .filter_map(|look_id| self.looks.get(look_id))
151
+ .find_map(|record| {
152
+ record
153
+ .nodes
154
+ .get(reference)
155
+ .cloned()
156
+ .map(|node| (record.clone(), node))
157
+ })
158
+ .ok_or_else(|| ProtocolError::new("Scope ref is stale", ErrorCode::StaleRef))
159
+ }
160
+
161
+ pub fn look(&self, look_id: &str) -> Result<LookRecord, ProtocolError> {
162
+ self.looks.get(look_id).cloned().ok_or_else(|| {
163
+ ProtocolError::new("Owning look is no longer available", ErrorCode::StaleLook)
164
+ })
165
+ }
166
+
167
+ pub fn element(&self, look_id: &str, reference: &str) -> Result<NodeSnapshot, ProtocolError> {
168
+ self.look(look_id)?
169
+ .nodes
170
+ .get(reference)
171
+ .cloned()
172
+ .ok_or_else(|| ProtocolError::new("Element reference is stale", ErrorCode::StaleRef))
173
+ }
174
+ }
175
+
176
+ pub fn fresh_state_id() -> String {
177
+ static NEXT: AtomicU64 = AtomicU64::new(1);
178
+ format!("w-{}", NEXT.fetch_add(1, Ordering::Relaxed))
179
+ }
180
+
181
+ #[cfg(test)]
182
+ mod tests {
183
+ use super::*;
184
+
185
+ fn accessible(n: usize) -> AccessibleRef {
186
+ AccessibleRef {
187
+ destination: format!(":1.{n}"),
188
+ path: format!("/node/{n}"),
189
+ }
190
+ }
191
+
192
+ fn root(n: usize) -> RootSnapshot {
193
+ RootSnapshot {
194
+ accessible: accessible(n),
195
+ pid: n as u64,
196
+ name: format!("root {n}"),
197
+ app_name: format!("app {n}"),
198
+ role: "frame".into(),
199
+ frame: None,
200
+ x11_window: None,
201
+ is_focused: false,
202
+ is_minimized: false,
203
+ z_order: None,
204
+ }
205
+ }
206
+
207
+ #[test]
208
+ fn root_refs_are_stable_and_dead_roots_are_removed() {
209
+ let mut state = HelperState::default();
210
+ let first = state.replace_roots(vec![root(1), root(2)]);
211
+ let second = state.replace_roots(vec![root(2)]);
212
+ assert_eq!(first[1].0, second[0].0);
213
+ assert!(state.root(&first[0].0).is_none());
214
+ }
215
+
216
+ #[test]
217
+ fn look_history_is_bounded_and_refs_are_look_scoped() {
218
+ let mut state = HelperState::default();
219
+ let mut first = String::new();
220
+ for n in 0..=MAX_LOOK_RECORDS {
221
+ let node = NodeSnapshot::minimal(accessible(n));
222
+ let (look, nodes) = state.insert_look(root(n), vec![node], None);
223
+ if n == 0 {
224
+ first = look.clone();
225
+ }
226
+ assert!(nodes[0].reference.starts_with(&format!("linux:{look}:")));
227
+ }
228
+ assert_eq!(state.look(&first).unwrap_err().code, ErrorCode::StaleLook);
229
+ }
230
+
231
+ #[test]
232
+ fn scoped_look_requires_scope_ref_owned_by_base_look() {
233
+ let mut state = HelperState::default();
234
+ let (first, first_nodes) =
235
+ state.insert_look(root(1), vec![NodeSnapshot::minimal(accessible(10))], None);
236
+ let (second, _) =
237
+ state.insert_look(root(1), vec![NodeSnapshot::minimal(accessible(20))], None);
238
+ assert_eq!(
239
+ state
240
+ .scope(&first_nodes[0].reference, Some(&second))
241
+ .unwrap_err()
242
+ .code,
243
+ ErrorCode::StaleRef
244
+ );
245
+ assert_eq!(
246
+ state
247
+ .scope(&first_nodes[0].reference, Some(&first))
248
+ .unwrap()
249
+ .1
250
+ .accessible,
251
+ accessible(10)
252
+ );
253
+ }
254
+
255
+ #[test]
256
+ fn scoped_look_carries_base_records_and_reuses_known_refs() {
257
+ let mut state = HelperState::default();
258
+ let mut parent = NodeSnapshot::minimal(accessible(10));
259
+ let mut child = NodeSnapshot::minimal(accessible(11));
260
+ child.parent = Some(parent.accessible.clone());
261
+ let (base_id, base_nodes) = state.insert_look(root(1), vec![parent.clone(), child], None);
262
+ let base = state.look(&base_id).unwrap();
263
+ parent.name = "updated".into();
264
+ let (scoped_id, scoped_nodes) = state.insert_look(root(1), vec![parent], Some(&base));
265
+ assert_eq!(scoped_nodes[0].reference, base_nodes[0].reference);
266
+ assert_eq!(
267
+ state
268
+ .element(&scoped_id, &base_nodes[1].reference)
269
+ .unwrap()
270
+ .accessible,
271
+ accessible(11)
272
+ );
273
+ assert_eq!(
274
+ state
275
+ .element(&scoped_id, &base_nodes[0].reference)
276
+ .unwrap()
277
+ .name,
278
+ "updated"
279
+ );
280
+ }
281
+
282
+ #[test]
283
+ fn scoped_look_preserves_base_image_size() {
284
+ let mut state = HelperState::default();
285
+ let node = NodeSnapshot::minimal(accessible(10));
286
+ let (base_id, _) = state.insert_look(root(1), vec![node.clone()], None);
287
+ state.set_look_image_size(&base_id, 1000, 500).unwrap();
288
+ let base = state.look(&base_id).unwrap();
289
+ let (scoped_id, _) = state.insert_look(root(1), vec![node], Some(&base));
290
+ let scoped = state.look(&scoped_id).unwrap();
291
+ assert_eq!((scoped.image_width, scoped.image_height), (1000, 500));
292
+ }
293
+ }
@@ -0,0 +1,89 @@
1
+ //! Read-only Wayland portal capability diagnostics.
2
+ //!
3
+ //! Runtime capture and input are intentionally unsupported. The probe only
4
+ //! reads D-Bus properties and never creates a portal request or session.
5
+
6
+ use std::fmt;
7
+
8
+ use zbus::{Connection, Proxy};
9
+
10
+ const DESTINATION: &str = "org.freedesktop.portal.Desktop";
11
+ const DESKTOP_PATH: &str = "/org/freedesktop/portal/desktop";
12
+ const REMOTE_DESKTOP: &str = "org.freedesktop.portal.RemoteDesktop";
13
+ const SCREEN_CAST: &str = "org.freedesktop.portal.ScreenCast";
14
+ const CURSOR_HIDDEN: u32 = 1;
15
+
16
+ #[derive(Debug)]
17
+ pub enum PortalError {
18
+ Unavailable(String),
19
+ }
20
+
21
+ impl fmt::Display for PortalError {
22
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23
+ match self {
24
+ Self::Unavailable(message) => write!(f, "desktop portal unavailable: {message}"),
25
+ }
26
+ }
27
+ }
28
+
29
+ impl std::error::Error for PortalError {}
30
+
31
+ fn unavailable(error: impl fmt::Display) -> PortalError {
32
+ PortalError::Unavailable(error.to_string())
33
+ }
34
+
35
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
36
+ pub struct PortalCapabilities {
37
+ pub remote_desktop_version: u32,
38
+ pub screen_cast_version: u32,
39
+ pub available_device_types: u32,
40
+ pub available_source_types: u32,
41
+ pub available_cursor_modes: u32,
42
+ }
43
+
44
+ #[derive(Clone)]
45
+ pub struct PortalClient {
46
+ connection: Connection,
47
+ }
48
+
49
+ impl PortalClient {
50
+ pub async fn connect() -> Result<Self, PortalError> {
51
+ Ok(Self {
52
+ connection: Connection::session().await.map_err(unavailable)?,
53
+ })
54
+ }
55
+
56
+ pub async fn probe(&self) -> Result<PortalCapabilities, PortalError> {
57
+ let remote = Proxy::new(&self.connection, DESTINATION, DESKTOP_PATH, REMOTE_DESKTOP)
58
+ .await
59
+ .map_err(unavailable)?;
60
+ let screen = Proxy::new(&self.connection, DESTINATION, DESKTOP_PATH, SCREEN_CAST)
61
+ .await
62
+ .map_err(unavailable)?;
63
+ let remote_desktop_version = remote.get_property("version").await.map_err(unavailable)?;
64
+ let available_device_types = remote
65
+ .get_property("AvailableDeviceTypes")
66
+ .await
67
+ .map_err(unavailable)?;
68
+ let screen_cast_version = screen.get_property("version").await.map_err(unavailable)?;
69
+ let available_source_types = screen
70
+ .get_property("AvailableSourceTypes")
71
+ .await
72
+ .map_err(unavailable)?;
73
+ let available_cursor_modes = if screen_cast_version >= 2 {
74
+ screen
75
+ .get_property("AvailableCursorModes")
76
+ .await
77
+ .map_err(unavailable)?
78
+ } else {
79
+ CURSOR_HIDDEN
80
+ };
81
+ Ok(PortalCapabilities {
82
+ remote_desktop_version,
83
+ screen_cast_version,
84
+ available_device_types,
85
+ available_source_types,
86
+ available_cursor_modes,
87
+ })
88
+ }
89
+ }