@widgex/wgx 0.0.0-stage → 0.4.2

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 (131) hide show
  1. package/LICENSE.txt +49 -0
  2. package/README.md +33 -2
  3. package/ThirdPartyNotices.txt +4685 -0
  4. package/package.json +30 -4
  5. package/provider-clis.json +1211 -0
  6. package/python/XACTOR.md +146 -0
  7. package/python/browser_actor_host.py +456 -0
  8. package/python/cdp_page.py +363 -0
  9. package/python/harness_check.py +77 -0
  10. package/python/requirements.txt +1 -0
  11. package/python/test_line_endings.py +185 -0
  12. package/python/xactor/__init__.py +18 -0
  13. package/python/xactor/__main__.py +226 -0
  14. package/python/xactor/api.py +882 -0
  15. package/python/xactor/bridge.py +784 -0
  16. package/python/xactor/dom.py +117 -0
  17. package/python/xactor/man/add_card.md +89 -0
  18. package/python/xactor/man/agent-vs-fork.md +108 -0
  19. package/python/xactor/man/agent.md +333 -0
  20. package/python/xactor/man/ask_model.md +110 -0
  21. package/python/xactor/man/ask_reference.md +56 -0
  22. package/python/xactor/man/ask_user.md +103 -0
  23. package/python/xactor/man/attach_app.md +595 -0
  24. package/python/xactor/man/attach_ide.md +65 -0
  25. package/python/xactor/man/attach_ide_surface.md +31 -0
  26. package/python/xactor/man/await_user.md +214 -0
  27. package/python/xactor/man/bash.md +142 -0
  28. package/python/xactor/man/browser-driver.md +137 -0
  29. package/python/xactor/man/browser_activity.md +53 -0
  30. package/python/xactor/man/browser_attach.md +84 -0
  31. package/python/xactor/man/browser_close_tab.md +50 -0
  32. package/python/xactor/man/browser_detach.md +50 -0
  33. package/python/xactor/man/browser_exec.md +66 -0
  34. package/python/xactor/man/browser_focus_tab.md +47 -0
  35. package/python/xactor/man/browser_hybrid.md +47 -0
  36. package/python/xactor/man/browser_open.md +50 -0
  37. package/python/xactor/man/browser_open_tab.md +58 -0
  38. package/python/xactor/man/browser_plumbing.md +107 -0
  39. package/python/xactor/man/browser_screenshot.md +56 -0
  40. package/python/xactor/man/browser_snapshot.md +52 -0
  41. package/python/xactor/man/browser_tabs.md +67 -0
  42. package/python/xactor/man/bulletin.md +88 -0
  43. package/python/xactor/man/capture_page.md +54 -0
  44. package/python/xactor/man/cd.md +64 -0
  45. package/python/xactor/man/chess_move.md +53 -0
  46. package/python/xactor/man/children.md +89 -0
  47. package/python/xactor/man/close_task.md +14 -0
  48. package/python/xactor/man/compact.md +75 -0
  49. package/python/xactor/man/drive.md +140 -0
  50. package/python/xactor/man/drive_space.md +72 -0
  51. package/python/xactor/man/driving.md +168 -0
  52. package/python/xactor/man/edit_file.md +112 -0
  53. package/python/xactor/man/finding.md +194 -0
  54. package/python/xactor/man/fork_agent.md +45 -0
  55. package/python/xactor/man/glob.md +83 -0
  56. package/python/xactor/man/grep.md +115 -0
  57. package/python/xactor/man/harness.md +165 -0
  58. package/python/xactor/man/harness_check.md +70 -0
  59. package/python/xactor/man/harness_format.md +257 -0
  60. package/python/xactor/man/highlight.md +66 -0
  61. package/python/xactor/man/instruct.md +111 -0
  62. package/python/xactor/man/ls.md +63 -0
  63. package/python/xactor/man/man.md +86 -0
  64. package/python/xactor/man/merge_wiz.md +40 -0
  65. package/python/xactor/man/notebook.md +98 -0
  66. package/python/xactor/man/overview.md +89 -0
  67. package/python/xactor/man/permissions.md +137 -0
  68. package/python/xactor/man/phase.md +150 -0
  69. package/python/xactor/man/powershell.md +74 -0
  70. package/python/xactor/man/profiles.md +60 -0
  71. package/python/xactor/man/project_drive.md +49 -0
  72. package/python/xactor/man/read_bulletin.md +89 -0
  73. package/python/xactor/man/read_file.md +90 -0
  74. package/python/xactor/man/relay.md +216 -0
  75. package/python/xactor/man/reply.md +72 -0
  76. package/python/xactor/man/reply_message.md +78 -0
  77. package/python/xactor/man/report_bug.md +118 -0
  78. package/python/xactor/man/request_answer.md +142 -0
  79. package/python/xactor/man/request_merge_decisions.md +121 -0
  80. package/python/xactor/man/request_operator_action.md +152 -0
  81. package/python/xactor/man/request_review.md +122 -0
  82. package/python/xactor/man/request_user_action.md +126 -0
  83. package/python/xactor/man/return_card.md +71 -0
  84. package/python/xactor/man/run.md +99 -0
  85. package/python/xactor/man/sandbox.md +144 -0
  86. package/python/xactor/man/session_fork.md +74 -0
  87. package/python/xactor/man/session_harness.md +109 -0
  88. package/python/xactor/man/session_join.md +102 -0
  89. package/python/xactor/man/session_land.md +73 -0
  90. package/python/xactor/man/session_nudge.md +73 -0
  91. package/python/xactor/man/session_rest.md +73 -0
  92. package/python/xactor/man/session_scope.md +84 -0
  93. package/python/xactor/man/session_terminate.md +58 -0
  94. package/python/xactor/man/session_token.md +78 -0
  95. package/python/xactor/man/sessions.md +93 -0
  96. package/python/xactor/man/set_harness.md +15 -0
  97. package/python/xactor/man/spawn_swarm.md +80 -0
  98. package/python/xactor/man/submit_evidence.md +54 -0
  99. package/python/xactor/man/swarm_brief.md +30 -0
  100. package/python/xactor/man/task_status.md +82 -0
  101. package/python/xactor/man/tasks.md +47 -0
  102. package/python/xactor/man/todo_write.md +104 -0
  103. package/python/xactor/man/update_card.md +70 -0
  104. package/python/xactor/man/use_profile.md +90 -0
  105. package/python/xactor/man/use_widget.md +82 -0
  106. package/python/xactor/man/wait_for_task.md +105 -0
  107. package/python/xactor/man/widget-model.md +284 -0
  108. package/python/xactor/man/widgets.md +59 -0
  109. package/python/xactor/man/write_file.md +76 -0
  110. package/python/xactor/man/writing-a-widget.md +142 -0
  111. package/python/xactor/man/xylon.md +120 -0
  112. package/python/xactor/man/xylon_blame.md +80 -0
  113. package/python/xactor/man/xylon_read.md +131 -0
  114. package/python/xactor/man/xylon_search.md +125 -0
  115. package/python/xactor/manual.py +195 -0
  116. package/python/xactor/parent_watch.py +115 -0
  117. package/python/xactor/plan_mode.py +350 -0
  118. package/python/xactor/sandbox.py +856 -0
  119. package/python/xactor/seals.py +438 -0
  120. package/python/xactor/shell_hints.py +210 -0
  121. package/python/xactor/states/__init__.py +13 -0
  122. package/python/xactor/states/base.py +523 -0
  123. package/python/xactor/states/registry.py +274 -0
  124. package/python/xactor/stdlib_api.py +6345 -0
  125. package/python/xactor/widgets/__init__.py +1 -0
  126. package/python/xactor/widgets/github_tokens.py +314 -0
  127. package/python/xactor/widgets/google_aistudio.py +365 -0
  128. package/settings-schema.json +403 -0
  129. package/viewer/index.css +160 -0
  130. package/viewer/index.js +15 -0
  131. package/wgx.mjs +290659 -0
@@ -0,0 +1,274 @@
1
+ """StateRegistry — maps URL patterns to PageState templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, TYPE_CHECKING
6
+
7
+ from .base import PageState, Observation, Transition, Step
8
+
9
+ if TYPE_CHECKING:
10
+ from ..api import ElementaryAPI
11
+ from ..dom import DOMSnapshot
12
+
13
+
14
+ class StateRegistry:
15
+ """Singleton registry mapping URL patterns → PageState templates.
16
+
17
+ Uses class-level storage so it works with both import and exec loading.
18
+
19
+ Also the source of truth for "where is the agent right now" — resolve()
20
+ publishes every change over the bridge so the IDE can render it live.
21
+ """
22
+
23
+ _states: dict[str, PageState] = {}
24
+ _adjacency: dict[str, list[str]] = {}
25
+
26
+ # Live position, published to the IDE on change.
27
+ _current: str | None = None
28
+ _current_url: str | None = None
29
+ # Fingerprint of the last graph we published, so the (large) graph payload
30
+ # rides along only when the registry itself changed.
31
+ _published_graph: tuple[str, ...] | None = None
32
+
33
+ @classmethod
34
+ def add(cls, state: PageState, transitions_to: list[str] | None = None) -> None:
35
+ """Register a state + its adjacency list."""
36
+ cls._states[state.name] = state
37
+ if transitions_to:
38
+ cls._adjacency[state.name] = transitions_to
39
+
40
+ # ── Live position reporting ──────────────────────────────────────
41
+
42
+ @classmethod
43
+ def graph(cls) -> dict[str, Any]:
44
+ """Serializable machine definition: states plus the adjacency graph."""
45
+ return {
46
+ "states": [s.describe() for s in cls._states.values()],
47
+ "adjacency": {k: list(v) for k, v in cls._adjacency.items()},
48
+ }
49
+
50
+ @classmethod
51
+ def _fingerprint(cls) -> tuple[str, ...]:
52
+ return tuple(sorted(cls._states)) + ("|",) + tuple(
53
+ f"{k}>{','.join(v)}" for k, v in sorted(cls._adjacency.items())
54
+ )
55
+
56
+ @classmethod
57
+ def publish(cls, state_name: str | None = None, url: str | None = None,
58
+ *, force: bool = False) -> None:
59
+ """Tell the IDE where the agent is. Never raises — reporting must not
60
+ break automation, and the browser backend can run with no bridge.
61
+ """
62
+ previous = cls._current
63
+ if state_name is not None:
64
+ cls._current = state_name
65
+ if url is not None:
66
+ cls._current_url = url
67
+
68
+ fingerprint = cls._fingerprint()
69
+ graph_changed = fingerprint != cls._published_graph
70
+ if not force and not graph_changed and previous == cls._current:
71
+ return # nothing new to say
72
+
73
+ payload: dict[str, Any] = {
74
+ "event": "xactor.state",
75
+ "state": cls._current,
76
+ "url": cls._current_url,
77
+ "previous": previous if previous != cls._current else None,
78
+ }
79
+ if graph_changed or force:
80
+ payload["graph"] = cls.graph()
81
+ cls._published_graph = fingerprint
82
+
83
+ try:
84
+ from .. import bridge
85
+
86
+ bridge._emit(payload)
87
+ except Exception:
88
+ pass
89
+
90
+ @classmethod
91
+ def current(cls) -> str | None:
92
+ """Last resolved state name, if any."""
93
+ return cls._current
94
+
95
+ @classmethod
96
+ async def here(cls, api: ElementaryAPI) -> PageState:
97
+ """The bound PageState for the page as it is right now.
98
+
99
+ Use this. `current()` returns a *name*, which you cannot call anything
100
+ on — getting something callable meant knowing to write
101
+ `await registry.resolve(await api.snapshot(), api)`, which was written
102
+ down nowhere. Agents that could not find it fell back to copying a
103
+ widget's observation JS into `api.evaluate`, which is the thing the
104
+ whole layer exists to make unnecessary.
105
+
106
+ state = await registry.here(api)
107
+ rows = await state.observe("existing_tokens")
108
+ state = await state.execute("create_token")
109
+ """
110
+ return await cls.resolve(None, api)
111
+
112
+ @classmethod
113
+ async def enter(cls, api: ElementaryAPI, state_name: str | None = None) -> PageState:
114
+ """Navigate to a state's `entry` URL and resolve.
115
+
116
+ This is how a widget is entered cold. With no name, the first registered
117
+ state that declares an `entry` is the home.
118
+
119
+ Resolution is deliberately not asserted against `state_name`: arriving
120
+ somewhere other than where you aimed — a login wall, a consent screen —
121
+ is information the caller needs, not an error to raise. Check
122
+ `.name` on what you get back.
123
+ """
124
+ if state_name is None:
125
+ enterable = [s for s in cls._states.values() if getattr(s, "entry", None)]
126
+ if not enterable:
127
+ raise ValueError(
128
+ "No loaded state declares an entry URL, so there is nowhere to "
129
+ "enter. Add entry=\"https://...\" to the widget's home state."
130
+ )
131
+ target = enterable[0]
132
+ else:
133
+ target = cls._states.get(state_name)
134
+ if target is None:
135
+ raise KeyError(
136
+ f"No state '{state_name}'. Loaded: {list(cls._states)}"
137
+ )
138
+ if not getattr(target, "entry", None):
139
+ raise ValueError(
140
+ f"State '{state_name}' has no entry URL — url_pattern is a matcher, "
141
+ "not a destination. Reach it with a transition, or give it entry=."
142
+ )
143
+
144
+ await api.navigate(target.entry)
145
+ snap = await api.wait_stable()
146
+ return await cls.resolve(snap, api)
147
+
148
+ @classmethod
149
+ async def resolve(cls, dom: "DOMSnapshot | None", api: ElementaryAPI) -> PageState:
150
+ """URL-match -> bind -> return. Falls back to UNKNOWN_STATE.
151
+
152
+ When several states match the same URL -- which is the normal case for a
153
+ site whose dialogs live at one address -- they are disambiguated by
154
+ scoring each state's keywords against the visible page text.
155
+
156
+ `dom` is accepted for the widgets that still pass one, but nothing needs
157
+ it any more: asking the page for its URL costs one cheap call, where a
158
+ snapshot used to pull the whole accessibility tree and every cookie on
159
+ the domain just to read `.url` off it.
160
+ """
161
+ if dom is not None and getattr(dom, "url", None):
162
+ url = dom.url
163
+ else:
164
+ state = await api.get_state()
165
+ url = (state or {}).get("url", "")
166
+
167
+ candidates = [s for s in cls._states.values() if s.matches_url(url)]
168
+ if not candidates:
169
+ cls.publish(UNKNOWN_STATE.name, url)
170
+ return UNKNOWN_STATE.bind(dom, api)
171
+ if len(candidates) == 1:
172
+ cls.publish(candidates[0].name, url)
173
+ return candidates[0].bind(dom, api)
174
+
175
+ # Several states claim this URL -- a site whose dialogs all live at one
176
+ # address. Score each one's keywords against what is actually rendered.
177
+ try:
178
+ page_text = await api.evaluate("document.body.innerText")
179
+ if not isinstance(page_text, str):
180
+ page_text = ""
181
+ except Exception:
182
+ page_text = ""
183
+ if not page_text and dom is not None:
184
+ page_text = dom.text_content()
185
+ page_text = page_text.lower()
186
+
187
+ best, best_score = candidates[0], -1
188
+ for state in candidates:
189
+ score = sum(1 for kw in state.keywords if kw.lower() in page_text)
190
+ if score > best_score:
191
+ best, best_score = state, score
192
+ cls.publish(best.name, url)
193
+ return best.bind(dom, api)
194
+
195
+ @classmethod
196
+ def all_states(cls) -> list[dict[str, Any]]:
197
+ """Return descriptions of all registered states."""
198
+ return [s.describe() for s in cls._states.values()]
199
+
200
+ @classmethod
201
+ def reachable_from(cls, state_name: str) -> list[str]:
202
+ """Return state names reachable from the given state."""
203
+ return cls._adjacency.get(state_name, [])
204
+
205
+ @classmethod
206
+ def clear(cls) -> None:
207
+ """Remove all registered states."""
208
+ cls._states.clear()
209
+ cls._adjacency.clear()
210
+ cls._current = None
211
+ cls._current_url = None
212
+ cls._published_graph = None
213
+
214
+
215
+ # ── UnknownPageState (fallback) ──────────────────────────────────
216
+
217
+ UNKNOWN_STATE = PageState(
218
+ "UnknownPage",
219
+ url_pattern=".*",
220
+ keywords=["unknown", "generic", "fallback"],
221
+ observations=[
222
+ Observation(
223
+ "links",
224
+ description="All links on the page",
225
+ js="Array.from(document.querySelectorAll('a[href]')).map(a => ({text: a.textContent?.trim(), href: a.href})).filter(l => l.text)",
226
+ ),
227
+ Observation(
228
+ "buttons",
229
+ description="All buttons on the page",
230
+ js="Array.from(document.querySelectorAll('button, [role=\"button\"], input[type=\"submit\"]')).map(b => ({text: b.textContent?.trim() || b.value || b.getAttribute('aria-label'), type: b.tagName.toLowerCase()}))",
231
+ ),
232
+ Observation(
233
+ "inputs",
234
+ description="All input fields on the page",
235
+ js="Array.from(document.querySelectorAll('input:not([type=\"hidden\"]), textarea, select')).map(i => ({type: i.type || i.tagName.toLowerCase(), name: i.name, id: i.id, placeholder: i.placeholder, value: i.value}))",
236
+ ),
237
+ Observation(
238
+ "headings",
239
+ description="All headings on the page",
240
+ js="Array.from(document.querySelectorAll('h1, h2, h3')).map(h => ({level: h.tagName, text: h.textContent?.trim()}))",
241
+ ),
242
+ Observation(
243
+ "title",
244
+ description="Page title",
245
+ js="document.title",
246
+ ),
247
+ ],
248
+ transitions=[
249
+ Transition(
250
+ "click_element",
251
+ description="Click any element by selector",
252
+ params={"selector": "str"},
253
+ steps=[
254
+ Step("click", "$selector", expects_transition=True),
255
+ ],
256
+ ),
257
+ Transition(
258
+ "type_into",
259
+ description="Type text into an element",
260
+ params={"selector": "str", "text": "str"},
261
+ steps=[
262
+ Step("clear_and_type", "$selector", text="$text"),
263
+ ],
264
+ ),
265
+ Transition(
266
+ "navigate_to",
267
+ description="Navigate to a URL",
268
+ params={"url": "str"},
269
+ steps=[
270
+ Step("navigate", "$url"),
271
+ ],
272
+ ),
273
+ ],
274
+ )