@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,595 @@
1
+ # api.attach_app(surface=None, env=None, present=None, preparing=None, config=None, show=None) -> dict
2
+
3
+ <!-- group: Driving a surface -->
4
+
5
+ Launch this workspace's own application and drive it. **Requires permission.**
6
+
7
+ For a product that is not a web page: an Electron app, an iOS app on the
8
+ Simulator (`kind: ios`), or an Android app on the Emulator (`kind: android`).
9
+ `widgex.yml` names which with `kind`.
10
+
11
+ ## Why this exists rather than `drive`
12
+
13
+ A web app can be started any way at all — `npm run dev` in a shell, a deployed
14
+ URL, a file on disk — and reached *afterwards* with `api.drive(url, why)`.
15
+ Nothing about how it started has to be known.
16
+
17
+ An Electron app can only be attached to if `--remote-debugging-port` was on its
18
+ command line **at spawn**. There is no attaching to one that is already running.
19
+ So "how does this app start" stops being a convenience and becomes the
20
+ precondition of the app being observable at all — and that is why it is declared
21
+ in a file that everyone shares rather than improvised by whichever session got
22
+ there first.
23
+
24
+ ## Never the copy they are running
25
+
26
+ The user is usually working *in* the application you are changing, or in one
27
+ beside it. This call starts a **separate** copy, with its own profile and
28
+ debugging port, so they can judge your change without leaving what they were
29
+ doing.
30
+
31
+ **Never deploy into, overwrite, or ask for a reload of the application the user
32
+ is running** — not to check your work, not to "just quickly look", and not as a
33
+ fallback when attaching fails. A reload costs them their place and their train
34
+ of thought, and installing over a running app means every session on that
35
+ machine is now running your half-finished change. When attaching fails, report
36
+ it and say what you could not confirm: a wrong spec is worth fixing, and
37
+ restarting their editor to save yourself a diagnosis is not.
38
+
39
+ ## It builds first
40
+
41
+ `app.build` runs before every spawn, so what you attach to is the code you just
42
+ changed. If the build fails the call refuses rather than handing you the copy
43
+ that was already running — a preview of the previous build, presented as your
44
+ change, is worse than no preview.
45
+
46
+ ## Writing the file for a project that has none
47
+
48
+ Work out how the app already starts — its package scripts, its launch
49
+ configuration, how a developer runs it by hand — and propose a `widgex.yml`
50
+ from that.
51
+
52
+ **Never ask the project to change itself to be testable.** No markers to add, no
53
+ preload script, no test-only build, no flag in their source. Selecting on markup
54
+ the app already renders is fine; requiring it to render something new for your
55
+ benefit is not. If a project cannot be previewed without changing it, say so —
56
+ that is a real answer, and a spec that quietly cannot work is not.
57
+
58
+ The requirement is one thing: a Chromium you can attach to. `app.kind` says
59
+ which of two ways it gets one:
60
+
61
+ - `kind: electron` — the app *is* Chromium and takes a debugging port on its
62
+ command line at spawn. There is no attaching to one already running, which is
63
+ why how it starts has to be declared.
64
+ - `kind: web` — `app.serve` boots the dev server and a browser is pointed at
65
+ `app.url`. Both carry `{port}`, which is allocated per preview: never write a
66
+ fixed one, because it works from a single worktree and then silently drives
67
+ that server from a second one.
68
+
69
+ Either way the app has to let its profile and single-instance behaviour be set
70
+ from outside, and show a surface you can pick out of markup it already renders.
71
+ A native binary or a canvas-only interface meets none of that and cannot be
72
+ driven by configuration alone. Say that plainly rather than writing a file that
73
+ will fail at the first attach.
74
+
75
+ ## The file
76
+
77
+ `widgex.yml`, at the workspace root. The minimum:
78
+
79
+ ```yaml
80
+ app:
81
+ kind: electron
82
+ exec: node_modules/.bin/electron
83
+ args: ['.']
84
+ ```
85
+
86
+ Everything else is optional:
87
+
88
+ ```yaml
89
+ app:
90
+ build: [pnpm, build] # run before a spawn, never before a reuse
91
+ debugPort: '--remote-debugging-port={port}'
92
+ envPolicy: inherit # or `allowlist` to keep only the bare minimum
93
+ env:
94
+ NODE_ENV: development
95
+ profile:
96
+ arg: --user-data-dir # or: env: SOME_APP_HOME
97
+ files: # seeded into the profile before the app starts
98
+ settings.json: '{"skipOnboarding": true}'
99
+ target:
100
+ urlIncludes: index.html
101
+ ```
102
+
103
+ ## Which machine it runs on
104
+
105
+ `surface` picks a window to look at. `env` picks the *machine* to run on, and
106
+ they are different questions.
107
+
108
+ ```yaml
109
+ app:
110
+ environments:
111
+ fresh:
112
+ description: A machine that has never run an agent CLI.
113
+ home: isolated
114
+ profile: fresh
115
+ returning:
116
+ home: inherit
117
+ ```
118
+
119
+ `home: isolated` gives the app its own home directory **and a different value
120
+ for `$USER`**, so it finds nothing of yours — no CLI config in your home, no
121
+ credential in your keychain. Both halves are needed and the second is the one
122
+ that is easy to miss: on macOS the keychain is per-user, not per-home, so a home
123
+ swap alone leaves a "fresh" machine still finding the credentials you signed in
124
+ with. Measured, not assumed.
125
+
126
+ Be precise about what the second half is, because this page used to say "its own
127
+ user" and that reads as more than it is. It is `HOME` and `USER`, two
128
+ environment variables. There is no account, no uid, nothing the kernel enforces.
129
+ It defeats a probe that reads `$USER` — which is exactly what Claude Code's
130
+ `security find-generic-password -a $USER` does, and why it works — and it
131
+ defeats nothing that asks the operating system who it really is. `PATH` is
132
+ inherited either way, so an isolated machine still *finds* your installed CLIs;
133
+ what it does not find is their credentials.
134
+
135
+ **`home: inherit` is not the default. Naming no environment at all is.** They
136
+ behave the same and they are not the same code path: with no `env=` the spec
137
+ carries no environment, so the block above is never consulted and `HOME`/`USER`
138
+ are never touched. The app runs here, on your machine, and sees everything you
139
+ see — your home, your CLIs, your keychain — while opening your own checkout.
140
+ A separate process with a separate profile and a separate port is not a separate
141
+ machine, and the attach result now says which you got.
142
+
143
+ `profile: fresh` throws the app's own state away before each launch. It is a
144
+ different question from `home`: `home` hides *your* machine from the app, and
145
+ the profile is what the environment's own last run left behind. Without it a
146
+ first-run test is a first run exactly once — the next launch restores a profile
147
+ that has already been through onboarding, and says nothing about it. `persist`
148
+ is the default, and is what `returning` wants.
149
+
150
+ Two more optional keys:
151
+
152
+ - **`files`** — seeded into the environment's home before the app starts.
153
+ Relative paths only, nothing escaping the root. `{home}` is substituted.
154
+ - **`setup`** — commands run once when the machine is built, each a list of
155
+ arguments rather than a shell string. Re-run only when `files`, `env` or
156
+ `setup` change; a failure refuses the attach rather than handing back a
157
+ half-built machine.
158
+
159
+ Each environment is its own instance and its own profile, so two of them run
160
+ side by side without seeing each other.
161
+
162
+ ### Why you want one
163
+
164
+ Testing a first run on your own machine does not test a first run. It tests
165
+ your machine. That mistake produced a report saying the setup wizard's
166
+ first-run flow was fine — it had never been exercised, because the wizard found
167
+ the developer's own CLI and opened on "already connected", and nothing in the
168
+ run said so.
169
+
170
+ ### Paths
171
+
172
+ `exec` is resolved against the workspace when it is relative. `{root}`
173
+ substitutes the workspace folder anywhere in `exec` or `args`, and `${VAR}`
174
+ expands from the environment the app will run with — an unset one is an error
175
+ rather than an empty string, because expanding it away produces a failure much
176
+ later that names nothing.
177
+
178
+ If `exec` names a macOS `.app` bundle, the executable inside it is found for
179
+ you; its name is the branding's, not something you should have to write down.
180
+
181
+ ### `profile.files`
182
+
183
+ Files written into the profile directory before the app starts, with `{port}`
184
+ substituted. Some apps cannot be tested without one — a marker that skips
185
+ onboarding, a preset config, a feature flag. Contents are written verbatim and
186
+ nothing is merged. Paths may not escape the profile directory.
187
+
188
+ WidgeX's own `widgex.yml` uses this for `argv.json`, which is how a spawned
189
+ instance learns the debugging port it was started with.
190
+
191
+ ### `debugPort` is a template
192
+
193
+ `{port}` is filled in. Put the literal token `{debugPort}` in `args` to control
194
+ *where* it lands, because position matters: `electron . --remote-debugging-port=N`
195
+ can reach the app as its own argv rather than Chromium's.
196
+
197
+ For a command whose arguments we cannot compose — `make dev`, a shell script, a
198
+ task runner — use the escape hatch:
199
+
200
+ ```yaml
201
+ debugPort:
202
+ env: WIDGEX_DEBUG_PORT
203
+ ```
204
+
205
+ Nothing is added to the command line; the variable is exported and **your script
206
+ must forward it**. That is one line in your own file, and it is the only way a
207
+ wrapped launch is testable at all. Forget it and the failure is "started but
208
+ never opened a window", which looks like four other things.
209
+
210
+ ### `profile`
211
+
212
+ Where the app keeps its own state, so two branches testing at once do not share
213
+ one. Omit it and they do.
214
+
215
+ ### `target` and `surfaces`
216
+
217
+ `target` is the default, used when you call `attach_app()` with no argument.
218
+ `surfaces` names things you can ask for individually:
219
+
220
+ ```yaml
221
+ surfaces:
222
+ prefs:
223
+ titleIncludes: Preferences
224
+ dashboard:
225
+ includeFrames: true
226
+ documentMatches: '#dashboard-root'
227
+ ```
228
+
229
+ Then `api.attach_app(surface='prefs')`. A name the file does not declare is
230
+ refused with the list of ones it does.
231
+
232
+ ### More than one app in one repository
233
+
234
+ `config` picks *which app*, where `surface` picks a window inside it. A
235
+ repository often holds two things worth previewing — a desktop app and the
236
+ website that documents it — and they share nothing: a different `kind`, a
237
+ different way of starting, different surfaces. So each goes under `apps:`, by
238
+ name, carrying a whole definition in the same shape as `app:`:
239
+
240
+ ```yaml
241
+ app: # the default, used when no config is named
242
+ kind: electron
243
+ exec: node_modules/.bin/electron
244
+
245
+ apps:
246
+ website:
247
+ kind: web
248
+ serve: [npm, run, docs, --, --port={port}]
249
+ url: http://localhost:{port}
250
+ ```
251
+
252
+ Then `api.attach_app(config='website')`. A name is a whole definition and not
253
+ a patch over `app:` — merging would mean every field needing a rule about
254
+ which side wins, and a reader never being sure what a name actually launches.
255
+
256
+ Two configurations are two instances, so a website preview and a desktop
257
+ preview can be up at once without either standing in for the other. A name the
258
+ file does not declare is refused with the ones it does; asking for one when
259
+ there is no `apps:` block prints the block to write.
260
+
261
+ Either can select three ways:
262
+
263
+ - `urlIncludes` / `titleIncludes` — free, straight out of the target listing.
264
+ - `documentMatches` — a CSS selector asked of the document itself, for targets
265
+ that cannot be told apart from outside. Costs a connection per candidate, so
266
+ it runs after the other two have narrowed the field. It reaches one frame deep,
267
+ because an app's real markup is often inside one.
268
+ - `includeFrames` — also consider frame targets. Off by default: an embedded
269
+ player or OAuth widget is a target too, and driving one by accident looks like
270
+ driving the app while touching almost none of it.
271
+
272
+ If several match and nothing distinguishes them, this refuses and lists them
273
+ rather than picking. An app with a splash screen would otherwise hand you the
274
+ splash screen, and every check you ran against it would pass.
275
+
276
+ WidgeX's own `widgex.yml` uses `documentMatches` for its `panel` and `chat`
277
+ surfaces: VS Code reports both as `vscode-webview://<uuid>` with an empty title,
278
+ so only the document can say which is which.
279
+
280
+ It also declares `workbench`, which is the *window* rather than a webview in it
281
+ — the command palette, the notifications, the tabs, the menus. Reach for it
282
+ whenever what you are verifying is a contributed command rather than something
283
+ drawn in a panel: a keystroke sent to a webview frame is delivered to the frame,
284
+ which has no keybinding service, so `api.press('Control+Shift+P')` on `panel`
285
+ does exactly nothing and says so in no way at all.
286
+
287
+ ```python
288
+ await api.attach_app(surface='workbench')
289
+ await api.press('Control+Shift+P')
290
+ await api.type_text('>My Command')
291
+ ```
292
+
293
+ The same shape applies to any Electron-hosted editor, and it is the reason the
294
+ app-target listing excludes only DevTools and extension pages rather than every
295
+ scheme an IDE uses internally: on the app's own debugging port, the window is
296
+ not "internal", it is the app.
297
+
298
+ A window surface is also the one you can screenshot directly. A frame is an
299
+ out-of-process iframe, and `Page.captureScreenshot` refuses one outright —
300
+ `api.screenshot()` on `panel` works only because it quietly shoots the window
301
+ containing it, and hands you the whole window rather than the panel.
302
+
303
+ ## An iOS app: `kind: ios`
304
+
305
+ For an app that runs on the iOS Simulator (Swift, React Native, Expo,
306
+ Flutter). On macOS with Xcode, `attach_app()` boots the simulator **without
307
+ opening Simulator.app**, builds, installs and launches the app. The device's
308
+ screen then shows up in a tab in this task's workbench, where the person can
309
+ tap, swipe, scroll and type into it. No window opens anywhere else.
310
+
311
+ ```yaml
312
+ app:
313
+ kind: ios
314
+ device: iPhone 17 # name or UDID; default: a booted iPhone, else the newest
315
+ build: [xcodebuild, -scheme, MyApp, -sdk, iphonesimulator,
316
+ -destination, 'generic/platform=iOS Simulator', -derivedDataPath, build]
317
+ app: build/Build/Products/Debug-iphonesimulator/MyApp.app # `*` allowed in a segment
318
+ bundleId: com.example.MyApp # optional: read from the .app when absent
319
+ ```
320
+
321
+ Every field is optional:
322
+ - With no `build`, the app was built some other way.
323
+ - With no `app`, the app is already installed and only `bundleId` is launched.
324
+ - With neither, you just get the device.
325
+
326
+ An Expo or React Native project whose own command builds and installs can leave
327
+ `app` out and launch by `bundleId`:
328
+
329
+ ```yaml
330
+ app:
331
+ kind: ios
332
+ build: [npx, expo, run:ios, --no-bundler]
333
+ bundleId: com.example.myapp
334
+ ```
335
+
336
+ `exec`, `args`, `serve` and `url` are refused: a simulator has no program to
337
+ run and no page to point at.
338
+
339
+ **Any booted simulator gets a tab, however it was booted.** If you boot one
340
+ with `xcrun simctl boot` or `npx expo run:ios` from `api.bash`, it appears in
341
+ the workbench of the task on screen within a few seconds. `attach_app` exists
342
+ so that the tab lands in *your* task and the app is built and launched for you.
343
+
344
+ **The page verbs do not reach a simulator.** `api.click`, `api.outline` and
345
+ friends are CDP, and a simulator has no markup. Use `xcrun simctl` through
346
+ `api.bash` with the returned `device.udid`:
347
+ - `xcrun simctl io <udid> screenshot shot.png` takes a picture you can look at.
348
+ - `xcrun simctl openurl <udid> <url>` follows a deep link.
349
+ - `xcrun simctl launch --terminate-running-process <udid> <bundleId>` relaunches the app.
350
+
351
+ To check that something works by touch, ask the person. They can use the
352
+ device in the tab.
353
+
354
+ Returns `{status: 'attached', kind: 'ios', device: {name, os, udid},
355
+ booted_by_widgex, stream_id, shown, note}`. `shown` says whether this task's
356
+ workbench is the one on screen.
357
+
358
+ ## An Android app: `kind: android`
359
+
360
+ The same idea for the Android Emulator, on any OS with the Android SDK
361
+ installed. `attach_app()` does five things:
362
+ 1. Starts the AVD **with no emulator window**, or uses it if it is already
363
+ running.
364
+ 2. Waits for Android to finish booting.
365
+ 3. Builds the app.
366
+ 4. Installs the APK.
367
+ 5. Opens the app.
368
+
369
+ The emulator's screen is a tab in this task's workbench. Tapping, swiping,
370
+ scrolling and typing all work there, and the tab has Back, Home, Recents and
371
+ Power.
372
+
373
+ ```yaml
374
+ app:
375
+ kind: android
376
+ avd: Pixel_8 # default: a running emulator, else the first AVD
377
+ build: [./gradlew, assembleDebug]
378
+ apk: app/build/outputs/apk/debug/app-debug.apk # `*` allowed in a segment
379
+ package: com.example.myapp # optional: read from the APK when absent
380
+ activity: .MainActivity # optional: default is the launcher activity
381
+ ```
382
+
383
+ For Expo or React Native, whose own command builds and installs:
384
+
385
+ ```yaml
386
+ app:
387
+ kind: android
388
+ build: [npx, expo, run:android, --no-bundler]
389
+ package: com.example.myapp
390
+ ```
391
+
392
+ Every field is optional, with the same meanings as for iOS. `exec`, `args`,
393
+ `serve` and `url` are refused.
394
+
395
+ **Any running emulator gets a tab, however it was started:** from `api.bash`
396
+ (`emulator -avd Pixel_8`), by `npx expo run:android`, or from Android Studio's
397
+ Device Manager.
398
+
399
+ Drive it with `adb -s <serial>` through `api.bash`. The page verbs do not
400
+ reach it.
401
+ - `adb -s emulator-5554 exec-out screencap -p > shot.png` takes a picture.
402
+ - `adb -s emulator-5554 shell am start -a android.intent.action.VIEW -d <url>`
403
+ follows a deep link.
404
+
405
+ Returns `{status: 'attached', kind: 'android', device: {name, os, avd,
406
+ serial}, booted_by_widgex, stream_id, shown, note}`.
407
+
408
+ ## Saying a demo is coming: `preparing`
409
+
410
+ The window opens before your first `instruct` is written, and writing one takes
411
+ however long you take. Until this, the app came up in front of somebody with
412
+ nothing on it to say why — and the first thing they saw was a card appearing out
413
+ of nowhere some seconds later.
414
+
415
+ So attaching draws the assistant straight away, saying what is being prepared:
416
+
417
+ ```python
418
+ await api.attach_app(surface='panel', preparing='the drafts autosave')
419
+ ```
420
+
421
+ Omit it and he says *"Preparing your demo…"*, which is true. He is not given a
422
+ feature name guessed from the surface: the surface is a panel, not the thing you
423
+ changed. Your first `instruct` replaces him wherever he has got to.
424
+
425
+ He is drawn in the **window**, not in the surface being driven, which is what
426
+ lets somebody drag him anywhere in the application rather than around one panel.
427
+ Where they put him outranks where the next step would have placed him.
428
+
429
+ ## How they see it: `present`
430
+
431
+ `present='window'` (the default) gives the app a real window on their screen —
432
+ once it is shown; see `show` below. Their pointer, their keyboard, their
433
+ cursor, at whatever rate it composites.
434
+ Nothing is translating anything, so nothing can translate it wrongly. A guided
435
+ tour requires one: it draws its instruction and its ring inside the app the
436
+ person is using.
437
+
438
+ `present='machine'` runs the app on a guest machine of its own, with this
439
+ Issue's worktree mounted in.
440
+
441
+ There used to be a third, `mirror`, which hid the window and streamed a
442
+ screencast into a card. It is gone: `present='mirror'` and an
443
+ `app.presentations.mirror` entry are both refused. Omit `present` for a window.
444
+
445
+ ```python
446
+ await api.attach_app(surface='panel', present='window')
447
+ ```
448
+
449
+ The presentations are separate instances. Asking for one never hands you the
450
+ process another session is using — the presentation is part of the launch's
451
+ identity, the same way the environment is.
452
+
453
+ `app.present` in `widgex.yml` moves the default, and `app.presentations` says
454
+ what each one needs in the environment:
455
+
456
+ ```yaml
457
+ app:
458
+ present: window
459
+ presentations:
460
+ window: {}
461
+ ```
462
+
463
+ ## Whether they can see it: `show`
464
+
465
+ **The window starts hidden.** Attaching never puts it over their editor or
466
+ takes their keyboard, because most attaches are you checking something for
467
+ yourself, and a window that jumps in front of somebody mid-sentence is worse
468
+ than one they have to ask for. You can drive it, read it and screenshot it
469
+ while it is hidden.
470
+
471
+ It comes up when a person is meant to look at it. That is either of two things:
472
+
473
+ - **You say so when you attach.** `show=True` brings it up straight away. Use
474
+ it when you attached in order to show them something.
475
+ `preparing='…'` announces a demo, so it implies `show=True` unless you also
476
+ pass `show=False`.
477
+ - **You ask them something about it.** These bring the window up by
478
+ themselves, once, when they ask:
479
+ - the first `instruct` of a tour;
480
+ - `request_operator_action`;
481
+ - `phase_complete`, for an app the lead attached.
482
+
483
+ So a check that ends in one of these needs no `show` at all.
484
+
485
+ A preview started from the panel's own button is shown, since a person pressed
486
+ it. "Show me the app" on a chat row brings it up again at any time.
487
+
488
+ ```python
489
+ await api.attach_app(surface='panel') # for yourself: stays hidden
490
+ await api.attach_app(surface='panel', show=True) # to show them: comes up now
491
+ ```
492
+
493
+ Leave `show` off for anything nobody is watching: a check you are running on
494
+ your own, a build you are verifying, a loop of attaches. `cold-attach-runs.ps1`
495
+ and `drive-attach-check.mjs` never show anything; they launch through the
496
+ instance registry, not through this call.
497
+
498
+ ### What "shown" means on each platform
499
+
500
+ - **macOS** — the window covers the display WidgeX is on, in WidgeX's Space,
501
+ above the menu bar, and takes the keyboard. That is the only way to be in
502
+ front of a full-screen WidgeX.
503
+ - **Windows and Linux** — an ordinary window over WidgeX's own rectangle, on
504
+ WidgeX's display. It is on top for under a second, so it actually comes
505
+ in front, and then it is not. Clicking WidgeX, Alt-Tab and the taskbar work
506
+ the way they do for any window. It never covers the whole screen.
507
+
508
+ Either way it is put back on a real display, so a window that last closed on a
509
+ monitor that is no longer there does not come up off-screen (which looks like a
510
+ white or missing window).
511
+
512
+ ### The ways out
513
+
514
+ Each still works on a shown preview:
515
+
516
+ - **"Back to WidgeX · or press Esc twice"**, the tab at the top of the app.
517
+ Both are handled inside the app, so they work even after the editor that
518
+ attached it has reloaded.
519
+ - **WidgeX's own button over the preview**, on macOS. The app cannot cover it.
520
+
521
+ Showing the window does not change what you are driving, and putting it away
522
+ does not detach you. The preview is hidden again and you carry on.
523
+
524
+ ## Permission
525
+
526
+ Gated, because `build` and `exec` are commands and `widgex.yml` is a file like
527
+ any other — it can arrive on a branch or in a pull request. This is `api.bash`
528
+ with extra steps and it asks the same way.
529
+
530
+ `api.attach_ide_surface` is gated too, and was not always. That was defensible
531
+ while it launched from a spec hardcoded in WidgeX's own source: the command came
532
+ from us. It reads `widgex.yml` now, so it runs what a branch says to run, and
533
+ the distinction stopped being real.
534
+
535
+ ## One at a time
536
+
537
+ One instance per workspace and launch. A second session asking for the same app
538
+ waits for the lease rather than starting a second copy — the same rule as
539
+ browser tabs, for the same reason: two drivers on one surface produce confident
540
+ conclusions about a thing neither was looking at.
541
+
542
+ ## It can act
543
+
544
+ `can_act` comes back True. Nothing here is drawing the conversation you are
545
+ having — it is a throwaway instance, which is exactly what an agent should be
546
+ allowed to click.
547
+
548
+ (Attaching to WidgeX's own dashboard was once observable-only, for the good
549
+ reason that a misresolved click could close the view you were being watched
550
+ through. That stopped applying when the surface became a separate instance
551
+ rather than the window the user is sitting in front of.)
552
+
553
+ ## Returns
554
+
555
+ `{status, title, url, port, kind, can_act, environment, isolated, presentation, shown}`
556
+
557
+ Read `title` and `url`. They name the window it chose, which is the one thing
558
+ worth checking before you believe anything else you observe.
559
+
560
+ Read `shown` before you say anything to the user about what they can see. It
561
+ is the window's own answer: visible, not minimised, and on a display.
562
+
563
+ - `True` — it is in front of them. Do not tell them to go and open it.
564
+ - `False` — nobody can see it. That is the normal result without `show=True`,
565
+ and a `note` says so. With `show=True` you also get `shown_reason`, saying
566
+ why it did not come up. Pass that on rather than saying "it's open".
567
+ - `None` — nothing could be asked (a guest machine, or an app whose main
568
+ process cannot be reached). Do not claim either way.
569
+
570
+ `presentation` says what kind of window it is. It does not say whether the
571
+ window is on screen. Under `machine` there is nothing on their screen until
572
+ you ask for a card.
573
+
574
+ Then read `environment` and `isolated`, which name the *machine*. `environment`
575
+ is the name you passed, or `None` when you passed nothing; `isolated` says
576
+ whether the instance actually got a home of its own. They are not the same
577
+ question — an environment may declare `home: inherit` — which is why both are
578
+ returned rather than one.
579
+
580
+ `{'environment': None, 'isolated': False}` means the app is running as you, with
581
+ your home and your credentials, on your checkout. That is the default and it is
582
+ often what you want; it is never what you want when you are testing a first run.
583
+
584
+ With `reach=`, a `prelude` key rides along: `{replayed, of, ms, reached}`, plus
585
+ `stopped_at`, `wanted`, `outcome` and `saw` when it did not arrive. You do not
586
+ have to check `reached` — a route that stopped has already raised by the time
587
+ you hold this dict.
588
+
589
+ ## See also
590
+
591
+ - `api.attach_ide_surface(surface)` — the older spelling of
592
+ `attach_app(surface=…)`. Same code path; prefer this one.
593
+ - `api.drive(url, why)` — a web page, including a dev server
594
+ - `api.phase_complete(...)` — ask for the phase's review once the app shows it
595
+ - `api.request_operator_action(...)` — hand the running app to the user for a step that is theirs
@@ -0,0 +1,65 @@
1
+ <!-- also: self, support, sprite -->
2
+ <!-- group: Working on a website -->
3
+
4
+ # api.attach_ide(surface='workbench') -> dict
5
+
6
+ The Support Sprite starts attached to `column`; pass a surface to move.
7
+
8
+ Drive the WidgeX window this session lives in — the one the user is sitting
9
+ at. **Support Sprite only**: any other session is refused, and should use
10
+ `attach_app`, which starts a separate copy nobody is using.
11
+
12
+ Nothing is launched. The running IDE already listens on a debugging port, and
13
+ this attaches to *its own* window on that port, so a ring drawn here is drawn
14
+ in front of the person you are helping, and a click here is a click they see.
15
+
16
+ ## Options
17
+
18
+ - **`surface`** — which part of the window the page verbs see:
19
+
20
+ | surface | what it is |
21
+ |---|---|
22
+ | `column` | **the WidgeX column — start here.** The side bar on the left, whatever it is showing: the task sidebar down its left edge (folder picker, Settings cog, Board, Main, Your tasks) and the page beside it — a conversation with its Agent / Files / Source Control / Overview tabs, or a Settings page. |
23
+ | `workbench` | VS Code itself: menus, editor tabs, the status bar, notifications. The column shows here only as an empty frame titled "WidgeX". |
24
+ | `panel` | a WidgeX page in the editor area — the board, or a Settings page someone moved there with Settings → Layout. Absent unless one is open. |
25
+ | `chat` | an agent's chat page, wherever it is drawn — usually the column, or an editor tab opened with "Open in editor". |
26
+ | `lead` | the project conversation (Main), in the column. |
27
+ | `xylon` | the Xylon manager. |
28
+
29
+ That is the default layout; Settings → Layout can move panels. A panel titled
30
+ "CHAT" on the right, if one is ever showing, is VS Code's own chat — it is not
31
+ part of WidgeX and none of these surfaces is in it.
32
+
33
+ Each webview is a **separate document**. Attached to `workbench`, the verbs see
34
+ VS Code's chrome and *not* the inside of the column; attached to `column`, the
35
+ reverse. Re-attach to move between them — it is cheap.
36
+
37
+ ## Returns
38
+
39
+ `{"status": "attached", "surface": ..., "url": ...}`, or `{"error": "…"}`:
40
+
41
+ | what `error` says | what to do |
42
+ |---|---|
43
+ | `no debugging port` | WidgeX was started without one. Tell the user; it needs a restart after WidgeX adds it. |
44
+ | `… is not open in this window` | open it first from the column — the Settings cog, Board, or a task in Your tasks — then attach again. |
45
+
46
+ ## Examples
47
+
48
+ Walking someone to their models:
49
+
50
+ ```python
51
+ await api.attach_ide("column")
52
+ await api.instruct("Click the Settings cog, at the top of the task sidebar.",
53
+ api.find(role="button", text_exact="Settings"))
54
+ r = await api.await_user(api.find(role="button", text_exact="Settings"))
55
+
56
+ await api.instruct("Now Model configuration, in the Settings list.",
57
+ api.find(role="tab", text_exact="Model configuration"))
58
+ ```
59
+
60
+ Prefer this to a palette command: most "WidgeX: …" commands predate the
61
+ column and do not land where the feature lives now.
62
+
63
+ ## See also
64
+
65
+ `highlight`, `instruct`, `await_user`, `attach_app`, `driving`
@@ -0,0 +1,31 @@
1
+ # api.attach_ide_surface(surface='panel') -> dict
2
+ <!-- also: ide-surface, dashboard, panel-surface, drive-the-ide -->
3
+ <!-- group: Working on a website -->
4
+
5
+ **The older spelling of `api.attach_app(surface=…)`.** Use that one — see
6
+ `api.man('attach_app')`. This name is kept because sessions and notes from
7
+ before the rename still use it; there is no second code path behind it.
8
+
9
+ It does what `attach_app` does: starts a **separate copy** of WidgeX from the
10
+ workspace's `widgex.yml` and attaches to the named surface in it. It does not
11
+ attach to the window the user is working in — only the Support Sprite does
12
+ that, with `attach_ide`.
13
+
14
+ What it used to say no longer holds: the surface is **not** read-only. The copy
15
+ is a throwaway nobody else is using, so the page verbs act on it like any other
16
+ page.
17
+
18
+ ## Options
19
+
20
+ - **`surface`** — a name from `app.surfaces` in `widgex.yml`; defaults to
21
+ `"panel"`. In WidgeX's own: `panel` is the dashboard (an editor tab in the
22
+ middle of the window), `chat` is an agent's chat page (usually drawn in the
23
+ WidgeX column on the left), `column` is that column whatever it shows.
24
+
25
+ ## Returns
26
+
27
+ The same as `attach_app`.
28
+
29
+ ## See also
30
+
31
+ `attach_app`, `request_operator_action`