@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,226 @@
1
+ """Daemon entry point: python -m xactor --dispatcher-id <id> [--backend browser|stdlib]"""
2
+
3
+ import argparse
4
+ import asyncio
5
+ import os
6
+
7
+ # The host puts this package on PYTHONPATH rather than starting us inside it
8
+ # (a working folder inside a worktree keeps Windows from ever deleting the
9
+ # tree). `sys.path` was settled when the interpreter started, so the variable
10
+ # has done its job; put back what the user had, so agent commands spawned from
11
+ # here see their own environment rather than ours.
12
+ _PYTHONPATH_BEFORE = os.environ.pop("WIDGEX_PYTHONPATH_BEFORE", None)
13
+ if _PYTHONPATH_BEFORE is not None:
14
+ if _PYTHONPATH_BEFORE:
15
+ os.environ["PYTHONPATH"] = _PYTHONPATH_BEFORE
16
+ else:
17
+ os.environ.pop("PYTHONPATH", None)
18
+
19
+ from . import bridge, plan_mode # noqa: E402
20
+
21
+
22
+ def _start_browser_backend(dispatcher_id: str) -> None:
23
+ """Original browser automation backend — requires a CDP dispatcher."""
24
+ from .api import ElementaryAPI
25
+ from .dom import DOMSnapshot, HybridNode, HybridDiff
26
+ from .states import PageState, LoadingState, Observation, Transition, Step, StateRegistry
27
+
28
+ api = ElementaryAPI()
29
+
30
+ bridge.set_exec_scope({
31
+ "api": api,
32
+ "asyncio": asyncio,
33
+ # The host composes exec code for `run_transition`, and needs one safe
34
+ # way to hand structured parameters across that boundary. JSON is that
35
+ # way: `json.loads` of a string literal cannot be talked into
36
+ # evaluating anything, whereas a Python dict literal assembled by
37
+ # string concatenation is one `true` away from a NameError and one
38
+ # quote away from being an injection.
39
+ "json": __import__("json"),
40
+ "DOMSnapshot": DOMSnapshot,
41
+ "HybridNode": HybridNode,
42
+ "HybridDiff": HybridDiff,
43
+ # Layer 2
44
+ "PageState": PageState,
45
+ "LoadingState": LoadingState,
46
+ "Observation": Observation,
47
+ "Transition": Transition,
48
+ "Step": Step,
49
+ "registry": StateRegistry,
50
+ })
51
+
52
+
53
+ def _graft_browser_l1(api: object) -> None:
54
+ """Put the Layer 1 browser verbs on the stdlib `api`, as bound methods.
55
+
56
+ The browser backend used to be a *second* Python process whose only real
57
+ job was holding this namespace: the terminal already reached every browser
58
+ command through `api.browser_*`, so the extra interpreter existed so that
59
+ `api.click(...)` and `registry` would resolve somewhere. That made
60
+ `browser_exec` a courier — the terminal shipped a string of Python to
61
+ another process to run — and cost an interpreter spawn on every browser
62
+ open.
63
+
64
+ Grafting rather than nesting under `api.browser` keeps every man page and
65
+ every habit intact: the verbs read the same here as they did there.
66
+
67
+ Grafting bound methods, rather than delegating via `__getattr__`, is what
68
+ keeps `dir(api)` honest and tracebacks pointing at `ElementaryAPI`. The
69
+ queue state lives on the instance the methods are bound to, so `click()`
70
+ and the `drain()` that collects it stay on the same object.
71
+ """
72
+ from .api import ElementaryAPI
73
+
74
+ browser = ElementaryAPI()
75
+ # So a verb that hands `self` onward — `here()` building a PageState — gives
76
+ # it the whole api rather than this half of it. See `ElementaryAPI._surface`.
77
+ browser._merged = api
78
+ for name in dir(browser):
79
+ if name.startswith("_"):
80
+ continue
81
+ # The two surfaces are disjoint today (28 verbs against 58, no overlap).
82
+ # Nothing enforces that, and a later `StdlibAPI.select` would silently
83
+ # shadow the browser's — or be shadowed, depending on which way the
84
+ # loop ran. Refusing to start is the only failure mode here that cannot
85
+ # be mistaken for the verb simply misbehaving.
86
+ if hasattr(api, name):
87
+ raise RuntimeError(
88
+ f"Cannot merge browser Layer 1 into the shell: `api.{name}` "
89
+ f"already exists on StdlibAPI. Rename one of them."
90
+ )
91
+ # Wrapped, because this `setattr` is the reason these verbs have never
92
+ # been gated: `_gate_api_methods` decorates what is in the class body,
93
+ # and these arrive afterwards. `guard` returns the function untouched
94
+ # when Plan mode permits it, so the read verbs cost nothing.
95
+ setattr(api, name, plan_mode.guard(name, getattr(browser, name)))
96
+
97
+
98
+ def _start_stdlib_backend(cwd: str, sandbox_mode: str, fallback_cwd: str | None = None,
99
+ fence: str | None = None, allow: list[str] | None = None) -> None:
100
+ """Stdlib coding backend — file I/O, subprocess, search, sessions, xylon, browser."""
101
+ from .stdlib_api import StdlibAPI
102
+ from . import sandbox
103
+
104
+ # Install the sandbox BEFORE any user code can run
105
+ sandbox.configure(
106
+ cwd=cwd,
107
+ mode=sandbox_mode,
108
+ allow_subprocess=(sandbox_mode in ("permissive", "open")),
109
+ allow_network=(sandbox_mode == "open"),
110
+ # The task's other working copies (its slots on other boards): one
111
+ # task's folders, read and written without asking.
112
+ read_allow=list(allow or []),
113
+ write_allow=list(allow or []),
114
+ )
115
+ if fence is not None:
116
+ # A drive-channel task: strict paths rooted at its folder, so a read
117
+ # outside it is refused (and asked about) rather than allowed the way
118
+ # permissive allows it. Subprocesses stay on — `api.bash` is how the
119
+ # agent works, and the host asks about each command it runs — which
120
+ # strict alone would turn off. An empty fence is one the host could
121
+ # not name; it admits no folder, so every path is asked about.
122
+ sandbox.configure(cwd=fence or cwd, mode="strict", fence=fence, allow_subprocess=True)
123
+ sandbox.install()
124
+
125
+ from .dom import DOMSnapshot, HybridNode, HybridDiff
126
+ from .states import PageState, LoadingState, Observation, Transition, Step, StateRegistry
127
+
128
+ api = StdlibAPI(cwd=cwd, bridge=bridge, fallback_cwd=fallback_cwd)
129
+ _graft_browser_l1(api)
130
+
131
+ # os/Path/json stay bound for path and data manipulation; the operations
132
+ # they expose (open, os.remove, ...) are denied by the audit hook unless
133
+ # they happen inside an api.* call.
134
+ bridge.set_exec_scope({
135
+ "api": api,
136
+ "asyncio": asyncio,
137
+ # Re-export stdlib modules for convenience in exec scope.
138
+ #
139
+ # `os` is the sandboxed one. Every other way into the OS raises an
140
+ # audit event the hook above can deny; reading `os.environ` raises
141
+ # none, so it was the one part of the outside world agent code could
142
+ # reach directly, in every permission mode. See sandbox._GuardedOS —
143
+ # path manipulation, which is why `os` is bound at all, is untouched.
144
+ "os": sandbox.guarded_os(),
145
+ "Path": __import__("pathlib").Path,
146
+ "json": __import__("json"),
147
+ # Layer 2, formerly the browser process's whole reason to exist. Widget
148
+ # files are read by the host and arrive here as exec'd source, so they
149
+ # need these bound — they import nothing, by design, and could not
150
+ # import under `restrict_builtins` anyway.
151
+ "DOMSnapshot": DOMSnapshot,
152
+ "HybridNode": HybridNode,
153
+ "HybridDiff": HybridDiff,
154
+ "PageState": PageState,
155
+ "LoadingState": LoadingState,
156
+ "Observation": Observation,
157
+ "Transition": Transition,
158
+ "Step": Step,
159
+ "registry": StateRegistry,
160
+ }, restrict_builtins=True)
161
+
162
+
163
+ def main() -> None:
164
+ parser = argparse.ArgumentParser()
165
+ parser.add_argument("--dispatcher-id", default="stdlib")
166
+ parser.add_argument("--backend", choices=["browser", "stdlib"], default="browser")
167
+ parser.add_argument("--cwd", default=None, help="Working directory for stdlib backend")
168
+ parser.add_argument("--requested-cwd", default=None,
169
+ help="What the host originally asked for, for disclosure")
170
+ parser.add_argument("--fallback-cwd", default=None,
171
+ help="Directory to use when --cwd no longer exists")
172
+ parser.add_argument("--sandbox", default="permissive",
173
+ choices=["strict", "permissive", "open"],
174
+ help="Sandbox permission mode")
175
+ parser.add_argument("--fence", default=None,
176
+ help="The only folder file calls may use without asking")
177
+ parser.add_argument("--allow", action="append", default=[],
178
+ help="A folder of the same task, read and written without asking")
179
+ args = parser.parse_args()
180
+
181
+ # Before the sandbox goes on, which refuses some of what watching needs.
182
+ # See `parent_watch` for why EOF alone did not end these daemons.
183
+ from . import parent_watch
184
+ parent_watch.watch(parent_watch.leave)
185
+
186
+ if args.backend == "stdlib":
187
+ import os
188
+ cwd = args.cwd or os.getcwd()
189
+ # The host checked this a moment ago, but the directory can be gone by
190
+ # the time we start — and a daemon that dies here reports ENOENT against
191
+ # the interpreter, naming neither the session nor the missing path.
192
+ if not os.path.isdir(cwd):
193
+ for candidate in (args.fallback_cwd, os.path.expanduser("~")):
194
+ if candidate and os.path.isdir(candidate):
195
+ bridge._emit({
196
+ "event": "cwd_fallback",
197
+ "requested": args.requested_cwd or cwd,
198
+ "effective": candidate,
199
+ })
200
+ cwd = candidate
201
+ break
202
+ _start_stdlib_backend(cwd, args.sandbox, fallback_cwd=args.fallback_cwd, fence=args.fence, allow=args.allow)
203
+ else:
204
+ _start_browser_backend(args.dispatcher_id)
205
+
206
+ loop = asyncio.new_event_loop()
207
+ asyncio.set_event_loop(loop)
208
+
209
+ # The read loop is the only thing servicing stdin. If it ever stops, every
210
+ # bridge call still waiting is unresolvable and every later one is too — the
211
+ # session looks alive and answers nothing. It used to die silently; now its
212
+ # death is announced and the pending calls are failed rather than orphaned.
213
+ _read_task = loop.create_task(bridge._read_loop())
214
+ _read_task.add_done_callback(bridge._read_loop_stopped)
215
+
216
+ bridge._emit({
217
+ "event": "ready",
218
+ "dispatcher_id": args.dispatcher_id,
219
+ "backend": args.backend,
220
+ })
221
+
222
+ loop.run_forever()
223
+
224
+
225
+ if __name__ == "__main__":
226
+ main()