@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,111 @@
1
+ <!-- also: card, coach -->
2
+ <!-- group: Talking to the user -->
3
+
4
+ # api.instruct(text, target=None, expect=None, title="", step=None, total=None) -> dict
5
+
6
+ Put a sentence in the app, beside a control when you name one.
7
+
8
+ With a `target` the card is placed against that control with a pointer, and the
9
+ control is ringed for you. Without one it takes a corner, which is right for
10
+ "watch what happens next" and wrong for anything naming a button.
11
+
12
+ Pass `text=None` to take it down.
13
+
14
+ **Taking it down can hand you a question.** The card holds one typed while no
15
+ wait was armed, so `{"status": "cleared", "asked": "…"}` means somebody asked
16
+ while you were not listening and the demo ended before you answered. Say
17
+ something rather than letting the tour be their answer.
18
+
19
+ ## Options
20
+
21
+ - **`text`** — the instruction. Short and imperative: it sits beside a live app
22
+ and is skimmed, not read. **Name the control in the words as well as ringing
23
+ it** — the ring is dropped whenever the geometry cannot be proven, and a
24
+ sentence that leans on it breaks at exactly the moment the safety net fires.
25
+ - **`target`** — what the card is about. Ringed, and the card is anchored to it.
26
+ - **`expect`** — what they should see. Prose you are showing them; nothing
27
+ checks it.
28
+ - **`title`** — the eyebrow above the instruction. Say "detour" here when a
29
+ question has taken you off the route you were on.
30
+ - **`step`, `total`** — optional counter. Omit for a single instruction; pass
31
+ them when walking somebody through several and they will want to know how far
32
+ along they are.
33
+
34
+ ## Returns
35
+
36
+ `{"status": "shown", "box": {...}}`, or `{"status": "shown", "unmarked": ...}`
37
+ when the target could not be ringed — the card is still shown, and says why
38
+ nothing is marked, because an instruction naming a control with nothing marked
39
+ otherwise reads as the product being broken.
40
+
41
+ **`unmarked` takes any previous ring down with it.** A card saying nothing could
42
+ be marked, over a ring still framing the last step's control, is read as the
43
+ ring. A step naming no target at all leaves the ring alone, so `highlight` then
44
+ `instruct` still works.
45
+
46
+ **`{"unraised": ...}` means they may not be looking at it.** The first step of a
47
+ tour brings the app's window to the front, since a preview opens in its own
48
+ window and that window is usually *behind* the one the chat is in. Where it
49
+ cannot — a mirror has no window, and raising another process's is macOS-only —
50
+ this field carries the sentence to pass on. Say it: an unraised window is a step
51
+ running its whole timeout in front of nobody, reported afterwards as if they had
52
+ ignored you.
53
+
54
+ `{"error": ...}` means the step was **not drawn**, or was drawn where nobody can
55
+ see it — under a modal, or off the edge of the viewport. Either way nobody is
56
+ looking at anything. Do not carry on to the next step.
57
+
58
+ A target that is scrolled out of view comes back as `unmarked` rather than an
59
+ error: the words are still worth showing, the ring is not a claim that can be
60
+ made, and the fix is yours — scroll it into view and instruct again.
61
+
62
+ ## Examples
63
+
64
+ **A question is not answered with this call.** `reply` is, and the difference is
65
+ the whole of why it exists: `instruct` *is* a new step — it moves the ring,
66
+ resets the counter and replaces the words — so answering with it takes away the
67
+ very sentence somebody was asking you to explain.
68
+
69
+ ```python
70
+ r = await api.await_user(target)
71
+ if q := r.get("asked"):
72
+ await api.reply("Claude Code advertises named methods, so we show them.")
73
+ r = await api.await_user(target) # the same step, handed back
74
+ ```
75
+
76
+ Use `instruct` when the answer really is somewhere else — a question is allowed
77
+ to take the tour off course, and that is what `title="detour"` is for:
78
+
79
+ ```python
80
+ await api.instruct(
81
+ "That one installs the CLI — here it is.",
82
+ api.find(role="button", text_exact="Install"),
83
+ title="detour",
84
+ )
85
+ ```
86
+
87
+ Say the words rather than moving the ring whenever both would do. A step that
88
+ survives the question is a tour; a step replaced by its own answer is a script
89
+ starting again.
90
+
91
+ ## Getting it wrong
92
+
93
+ **Writing documentation.** The card clamps to two lines. Over about 140
94
+ characters is not read, it is cropped.
95
+
96
+ **An opening step that does nothing.** "Press Next to begin" is a step somebody
97
+ has to dismiss. Start on the first real action.
98
+
99
+ **Describing a screen you have not looked at.** Look at the app before you write
100
+ each step. A step written ahead of time is a prediction, and the tour is guided
101
+ live precisely so that it does not have to be one.
102
+
103
+ **Demoing the mechanism.** Narrating how tours work, over an app doing something
104
+ unrelated, demonstrates nothing. Point at the thing you changed.
105
+
106
+ **Leaving it up.** `instruct(None)` and `highlight()` when you are done. A card
107
+ outliving what it was about is how a finished tour reads as a broken one.
108
+
109
+ ## See also
110
+
111
+ `reply`, `highlight`, `await_user`, `phase_complete`
@@ -0,0 +1,63 @@
1
+ # api.ls(path=None) -> list[dict]
2
+ <!-- group: Coding -->
3
+
4
+ List one directory. Not recursive. Not gated.
5
+
6
+ ## Options
7
+
8
+ - **`path`** — directory to list. Absolute, or relative to the shell's cwd.
9
+ Defaults to the cwd.
10
+
11
+ ## Returns
12
+
13
+ ```python
14
+ [{"name": str, "type": "dir" | "file", "size": int | None}, ...]
15
+ ```
16
+
17
+ sorted by name. `name` is the bare entry name, not a path — join it to `path`
18
+ yourself. `size` is bytes for files and **`None` for directories**.
19
+
20
+ One degenerate shape: an unreadable directory returns
21
+ `[{"error": "Permission denied: <path>"}]`, a normal return rather than a
22
+ raise. A path that does not exist *does* raise `FileNotFoundError`, and a path
23
+ that is a file raises `NotADirectoryError`.
24
+
25
+ ## Examples
26
+
27
+ ```python
28
+ return await api.ls("src")
29
+ ```
30
+
31
+ Just the subdirectories:
32
+
33
+ ```python
34
+ entries = await api.ls()
35
+ return [e["name"] for e in entries if e.get("type") == "dir"]
36
+ ```
37
+
38
+ Finding the largest files in a directory:
39
+
40
+ ```python
41
+ entries = await api.ls("dist")
42
+ files = [e for e in entries if e["type"] == "file"]
43
+ files.sort(key=lambda e: e["size"], reverse=True)
44
+ return files[:5]
45
+ ```
46
+
47
+ ## Getting it wrong
48
+
49
+ **Expecting recursion.** `ls` sees one level. Use `api.glob("**/*")` to walk a
50
+ tree.
51
+
52
+ **Sorting on `size` without filtering.** Directories carry `size: None`, and
53
+ `None` in a numeric sort raises `TypeError`. Filter to `type == "file"` first.
54
+
55
+ **Treating `name` as a path.** `api.read_file(e["name"])` resolves against the
56
+ shell's cwd, not against the directory you listed. Join them, or `api.cd` first.
57
+
58
+ **Using it to check existence.** It raises for a missing path. `api.glob` is
59
+ the non-raising way to ask.
60
+
61
+ ## See also
62
+
63
+ `glob`, `grep`, `cd`, `read_file`
@@ -0,0 +1,86 @@
1
+ # api.man(topic=None) -> str
2
+ <!-- group: Documentation -->
3
+
4
+ The manual page for an api call or a WidgeX feature. Not gated.
5
+
6
+ The tool description is an index of one-line glosses. This is where the options,
7
+ the return shapes, and the ways each call is usually got wrong actually live.
8
+
9
+ Reading a page costs one round trip. Guessing a return shape costs everything
10
+ built on top of the guess.
11
+
12
+ ## Options
13
+
14
+ - **`topic`** — an api call or feature name. Spelling is forgiving: `'bash'`,
15
+ `'api.bash'`, `'bash()'` and `'await api.bash'` all land on the same page, as
16
+ do `'session join'` and `'session_join'`. An unambiguous prefix resolves too,
17
+ so `'session_ext'` reaches `agent`. Omit it for the index of every
18
+ page.
19
+
20
+ ## Returns
21
+
22
+ The page, as markdown, in a `str`. Not a dict — there is no `['text']` to index
23
+ into.
24
+
25
+ **A topic that matches nothing does not raise.** It comes back as prose naming
26
+ the near misses:
27
+
28
+ ```
29
+ No manual page for 'sesion_join'.
30
+ Closest matches: session_join, session_rest, session_fork
31
+ All topics: api.man() lists them.
32
+ ```
33
+
34
+ This is deliberate. An exception would abort the whole exec and take the
35
+ surrounding work with it, which would make guessing the cheaper strategy.
36
+
37
+ ## Examples
38
+
39
+ Before the first use of a call:
40
+
41
+ ```python
42
+ return await api.man("edit_file")
43
+ ```
44
+
45
+ The full index:
46
+
47
+ ```python
48
+ return await api.man()
49
+ ```
50
+
51
+ Reading a page and carrying on in the same exec — safe, because a miss is a
52
+ string:
53
+
54
+ ```python
55
+ doc = await api.man("session_dae") # resolves by prefix
56
+ files = await api.glob("src/**/*.ts")
57
+ return {"doc_len": len(doc), "files": len(files)}
58
+ ```
59
+
60
+ A feature rather than a call:
61
+
62
+ ```python
63
+ return await api.man("agent-vs-fork")
64
+ ```
65
+
66
+ ## Getting it wrong
67
+
68
+ **Not calling it.** The tool description is one line per call. It is enough to
69
+ know a call exists and roughly nothing else. The pages exist because the
70
+ signature is not the contract.
71
+
72
+ **Calling it after the mistake.** The page for `edit_file` is worth reading
73
+ before the edit, not while explaining why the edit did not land.
74
+
75
+ **Indexing the result.** It is a string. `(await api.man('bash'))['text']` is a
76
+ `TypeError`.
77
+
78
+ **Assuming a topic must be exact.** Prefixes and loose spellings resolve. If
79
+ something genuinely does not exist, you get suggestions, not a failure.
80
+
81
+ **Reading every page.** They are documentation, not context. Read the one you
82
+ are about to use.
83
+
84
+ ## See also
85
+
86
+ `overview`, `sandbox`, `permissions`
@@ -0,0 +1,40 @@
1
+ <!-- group: Talking to the user -->
2
+ # api.merge_wiz(sources, target, pr) -> dict
3
+
4
+ Propose a merge. A card asks the person, and **Proceed** opens the Merge Wizard already set up.
5
+
6
+ ```python
7
+ r = await api.merge_wiz(["widgex/auth-tokens", "widgex/ui-theme"], target="main")
8
+ if r["proceeded"]:
9
+ ... # the wizard is open and theirs; carry on with something else
10
+ elif r.get("denied"):
11
+ ... # they pressed Abort: do not merge these any other way
12
+ ```
13
+
14
+ The card is headed **Merge these branches?** It draws every source branch as a
15
+ square name tag on the left, with arrows to the target's tag on the right:
16
+ solid for a direct merge, dashed for a pull request. Two buttons, and an Ask.
17
+
18
+ | Answer | Returns |
19
+ |---|---|
20
+ | Proceed | `{"proceeded": True}` |
21
+ | Abort | `{"proceeded": False, "denied": True}` |
22
+ | Ask | `{"proceeded": False, "asked": "<what they want to know>"}` |
23
+
24
+ **Proceed starts nothing.** It opens the wizard with `sources[0]` on the left
25
+ card, the rest under it, `target` on the right and the arrow on merge or pull
26
+ request, with the plan drawn: for more than one source, the conflict groups.
27
+ The person presses Start there, after reading it. So `proceeded` means "they
28
+ took it from here", not "it is merged".
29
+
30
+ **Abort is a no.** Do not merge those branches some other way, and do not ask
31
+ again unless something has changed.
32
+
33
+ - `sources` is a non-empty list of branch names, none repeated and none equal
34
+ to `target`. The host checks each one resolves to a commit and says which
35
+ does not, as a `ValueError` to fix and call again.
36
+ - `target` defaults to `"main"`. `pr=True` sets the arrow to a pull request.
37
+ - **It blocks** until they answer: raise the exec's `timeout_seconds` past a
38
+ few minutes. A card nobody answers expires, raising `RuntimeError`.
39
+ - Refused in Plan mode. A merge proposed from a plan nobody has approved is a
40
+ merge nobody has approved.
@@ -0,0 +1,98 @@
1
+ <!-- also: worked examples, refactor demo, non-visual, cells, show a function -->
2
+ <!-- group: Talking to the user -->
3
+
4
+ # api.notebook(title, cells, save=None) -> dict
5
+
6
+ Worked examples, for a change with nothing to look at. **Requires permission.**
7
+
8
+ ## Why
9
+
10
+ A walkthrough shows a feature by having somebody use it. A refactor, a type fix,
11
+ a new pure function, a build script — none of them has a surface to mirror, and
12
+ the standing advice for those is *"say what you did and stop"*. So the entire
13
+ confirmation apparatus was missing exactly where the diff is hardest to read.
14
+
15
+ This is the same idea through the terminal: a few concrete inputs, the real
16
+ output each produced, in reading order. It writes `.widgex/notebooks/<slug>.md`,
17
+ which is readable in the editor, in a pull request, and by somebody who has never
18
+ heard of WidgeX. There is no viewer to open.
19
+
20
+ ## It is not a test run
21
+
22
+ Writing it as one wastes it. *"31 tests passed"* tells a reader nothing about
23
+ whether the thing does what they meant — it tells them the assertions somebody
24
+ already wrote still hold.
25
+
26
+ `flattenPrelude` called on a two-step route, with its actual output printed
27
+ underneath, tells them whether the idea is right. That is the job: the same one a
28
+ walkthrough does for a feature, and just as far from being a test suite. A
29
+ walkthrough is not 100% coverage either; it is somebody being shown the point.
30
+
31
+ Pick examples that would make a reviewer say *"ah, that's what it does"*.
32
+
33
+ ```python
34
+ await api.notebook(
35
+ "How a prelude resolves a use: reference",
36
+ cells=[
37
+ {"note": "A route with two clickable steps, referenced by a feature "
38
+ "walkthrough. The reference expands to both of its clicks.",
39
+ "run": "node -e \"...\""},
40
+ {"note": "Its third step is `advance: manual` — an instruction, not an "
41
+ "action. Using it whole is refused, by name and with the "
42
+ "`upto` to write instead.",
43
+ "run": "node -e \"...\""},
44
+ ],
45
+ save="prelude-resolution",
46
+ )
47
+ ```
48
+
49
+ ## The commands run here
50
+
51
+ That is why this call exists rather than you writing the markdown yourself. An
52
+ agent can type `$ pnpm test` above the words "31 passed" without either having
53
+ happened. Here the command line and the exit code are recorded by the host, the
54
+ prose between them is yours, and the rendering keeps them apart — fenced,
55
+ labelled output; unfenced commentary.
56
+
57
+ A cell that exits non-zero is **kept and shown**. A worked example that fails is
58
+ still a worked example, and a notebook that dropped them would be a highlight
59
+ reel — the one shape this must not have, because a reader trusts it exactly as
60
+ far as it shows what really happened.
61
+
62
+ **Keep the command short.** A reader's eye has to get past it to reach the
63
+ output, so a 400-character `node -e "..."` one-liner buries the thing you are
64
+ trying to show. If the setup is long, put it in a small script and run that —
65
+ the command line is there to make the output checkable, not to be the content.
66
+
67
+ Each cell gets 120 seconds and its output is trimmed middle-out if it is very
68
+ long, so the tail — the result, the last row, the traceback — survives.
69
+
70
+ ## Options
71
+
72
+ - **`title`** — what these examples are showing, in one line.
73
+ - **`cells`** — in order. Each is `{"run": "<command>", "note": "<why>"}`. The
74
+ `note` is the prose above the output: say what to look at, not what the command
75
+ is. A reader can see the command.
76
+ - **`save`** — the slug to write. Defaults to a slug of the title.
77
+
78
+ ## Returns
79
+
80
+ ```python
81
+ {"saved_to": ".../.widgex/notebooks/prelude-resolution.md",
82
+ "name": "prelude-resolution", "cells": 2, "failed": 0}
83
+ ```
84
+
85
+ `failed` counts non-zero exits. They are in the document, not hidden.
86
+
87
+ ## Permission
88
+
89
+ Gated, because it runs commands straight out of a model's output — which is what
90
+ `api.bash` is gated for. That the commands are being recorded into a document a
91
+ human will read does not make them safer to run.
92
+
93
+ ## See also
94
+
95
+ - `api.request_review(claim, demo=…)` — hand it over when it is written
96
+ - `api.instruct(...)` / `api.await_user(...)` — the equivalent for something
97
+ with a surface: a tour given live, decided step by step
98
+ - `api.man('request_review')` — deciding what to raise for review at all
@@ -0,0 +1,89 @@
1
+ # The WidgeX api
2
+ <!-- also: index, help, api, getting-started -->
3
+ <!-- group: Concepts -->
4
+
5
+ Everything is an `api.*` call inside one Python shell. Start here.
6
+
7
+ There is one tool. Reading a file, running a command, asking the user a
8
+ question, driving a website, spawning a session — all of it is a coroutine on
9
+ `api`, awaited, in code you write. The tool description lists the calls in one
10
+ line each; every one of them has a page.
11
+
12
+ ```python
13
+ return await api.man("edit_file")
14
+ ```
15
+
16
+ ## The groups
17
+
18
+ **Coding** — `read_file` `write_file` `edit_file` `bash` `glob` `grep` `ls` `cd`.
19
+ The file calls are journalled and appear in the xylon diff; `bash` is not.
20
+
21
+ **Talking to the user** — `ask_user` (a multiple-choice question, blocking),
22
+ `request_user_action` (ask a human to do something you cannot), `todo_write`
23
+ (the task list shown in chat).
24
+
25
+ **Subagents** — `agent`. Blocks, returns a string, no session, no prompt.
26
+ Underused.
27
+
28
+ **Talking to other agents** — `read_bulletin` and `wait_for_task` for the shared
29
+ task board; `relay_connect` `relay_send` `relay_inbox` `relay_accept`
30
+ `relay_decline` `relay_close` for direct messaging. `force=True` on a connect is
31
+ orchestration of a direct child and nothing else.
32
+
33
+ **Sessions** — `agent` and `session_fork` create children;
34
+ `session_join` waits; `session_terminate` stops one; `session_harness` and
35
+ `session_rest` control unattended running.
36
+
37
+ **Xylon** — `xylon_list_trees` `xylon_get_tree` `xylon_get_node`
38
+ `xylon_read_messages` `xylon_get_diff`. Reads of the context tree.
39
+ `xylon_search` finds past tasks by what was said in them and `xylon_read_turns`
40
+ reads the turns around a hit — start there rather than with a whole transcript.
41
+
42
+ **Working on a website** — `widgets` first, then `use_widget` or `drive`, and
43
+ then act with the verbs on `api`: `outline` / `search` / `find` / `one` to
44
+ locate, `click` / `type_text` / `select` to act, `drain` for one report of the
45
+ whole chain. Actions queue — load a chain rather than awaiting each step. A
46
+ widget's machine is `registry`, bound here too. See `driving`, then
47
+ `finding`, `widget-model`, and `browser-driver` for handing a long session to a
48
+ smaller model.
49
+
50
+ **Reporting a WidgeX problem** — `report_bug`. For the IDE, not the project you
51
+ are editing.
52
+
53
+ **Documentation** — `man`.
54
+
55
+ ## Three rules
56
+
57
+ 1. **`await` everything.** Every api call is a coroutine.
58
+ 2. **`return` sends a value back.** Printing is not returning.
59
+ 3. **Only `api` reaches outside.** Imports, `open()` and direct `os.*` calls are
60
+ refused by the sandbox — see `sandbox`.
61
+
62
+ ## Gated calls
63
+
64
+ Some calls interrupt the user for approval: `bash`, `write_file`, `edit_file`,
65
+ `browser_exec`, `browser_screenshot`, `browser_snapshot`, `drive`, `use_widget`,
66
+ `agent`, `session_fork`, `session_terminate`, `session_harness`. Which ones prompt, and what happens under a harness, is on
67
+ the `permissions` page.
68
+
69
+ ## Getting it wrong
70
+
71
+ **Treating the tool description as the documentation.** It is one line per call.
72
+ The options, the return shapes and the failure modes are on the pages, and
73
+ several calls fail *silently* if you guess — `edit_file` returns an error string
74
+ rather than raising, `session_join` reports a crashed child as an empty answer,
75
+ `todo_write` replaces the whole list.
76
+
77
+ **Assuming an error will announce itself.** Several calls return a shape rather
78
+ than raising. Read the "Returns" section before building on a result.
79
+
80
+ **Reaching for `bash` for file work.** It is not journalled, so its changes are
81
+ invisible to the tree and cannot be reverted. Use the file calls.
82
+
83
+ **Spawning a session for something `agent` would answer.** See
84
+ `agent-vs-fork`.
85
+
86
+ ## See also
87
+
88
+ `man`, `sandbox`, `permissions`, `sessions`, `bulletin`, `relay`, `xylon`,
89
+ `widget-model`, `agent-vs-fork`, `harness`
@@ -0,0 +1,137 @@
1
+ # Permissions
2
+ <!-- group: Concepts -->
3
+
4
+ Which calls interrupt the user, and how a refusal reaches you.
5
+
6
+ Permission is enforced per api call, not per exec. The user approves *this
7
+ write to this path*, not "a block of Python that could do anything". That is
8
+ the reason there is one tool and everything is an `api.*` call.
9
+
10
+ ## The gated calls
11
+
12
+ ```
13
+ write_file edit_file bash
14
+ session_fork session_terminate session_harness
15
+ use_widget drive use_profile notebook
16
+ browser_open_tab browser_exec browser_snapshot browser_screenshot
17
+ attach_app attach_ide_surface
18
+ ```
19
+
20
+ Everything else runs without asking, including all reads (`read_file`, `glob`,
21
+ `grep`, `ls`), all of `xylon_*`, `widgets`, `todo_write`, `session_join`,
22
+ `session_rest`, `report_bug` and `man`.
23
+
24
+ **`agent` is the one call that is gated or not depending on how you call it.**
25
+ A waited subagent in your own tree — `api.agent(prompt)` — is bounded, runs in
26
+ your shell and is gone when it returns, and does not ask. Every other shape
27
+ leaves something running that acts on its own, and each of those asks:
28
+
29
+ ```
30
+ api.agent(prompt) # no prompt — waited, shared tree
31
+ api.agent(prompt, wait=False) # asks — outlives this call
32
+ api.agent(prompt, isolate=True) # asks — its own worktree
33
+ api.agent(prompt, raise_=True) # asks — an Issue is durable by design
34
+ ```
35
+
36
+ Two of the gated calls deserve a note on *why*:
37
+
38
+ - **`use_widget` / `drive`** gate reaching a site at all, not just running
39
+ code on it. Which of the two you call is itself the declaration — a named
40
+ automation, or an admission that there is none.
41
+ - **`browser_snapshot` / `browser_screenshot`** are gated because a snapshot
42
+ carries the page's cookies and a screenshot captures whatever the human left
43
+ signed in.
44
+
45
+ `widgets()` is deliberately *not* gated: listing what is installed opens
46
+ nothing and reveals nothing.
47
+
48
+ ## Plan mode
49
+
50
+ Plan mode is read-only, and it does not ask — it **refuses**. Nothing you do
51
+ while planning raises a card except `ExitPlanMode` itself.
52
+
53
+ So the gated list above is not the list that matters here. A different and
54
+ smaller set runs: reads (`read_file`, `glob`, `grep`, `ls`, `man`), the
55
+ page-reading verbs (`search`, `outline`, `one`, `snapshot`, `digest`), the
56
+ `xylon_*` tree, relay and the bulletin, `ask_user`, `ask_model`, `todo_write`,
57
+ `report_bug`, `add_card`, and a waited `api.agent(...)` — which is itself
58
+ plan-constrained, so research subagents work and writing ones do not.
59
+
60
+ Everything else refuses, including calls that are ungated in every other mode:
61
+ `bash_kill`, `cd`, `evaluate`, `screenshot`, the `request_*` family, and the
62
+ browser action verbs (`click`, `type_text`, `press`, `navigate`, …).
63
+
64
+ A refusal is not a decision you can change by asking again — it is the mode,
65
+ and every alternative spelling of the same action gets the same answer. Write
66
+ the plan and call `ExitPlanMode`; the user approves it, the mode changes, and
67
+ the call runs.
68
+
69
+ Because a refusal raises, it aborts the exec like any other — so batching a
70
+ read and an action together loses the read. While planning, keep the reads in
71
+ their own exec.
72
+
73
+ ## How a refusal arrives
74
+
75
+ A declined call raises `PermissionError`:
76
+
77
+ ```
78
+ Permission denied for bash(): the user declined this operation
79
+ ```
80
+
81
+ Because it raises, **it aborts the whole exec**. Everything after that line in
82
+ the same block does not run. That is the main practical consequence: a long
83
+ script with a gated call in the middle loses the work below it if the user says
84
+ no.
85
+
86
+ The validator for a call runs *before* the prompt, so the user is never asked
87
+ to approve something that could not have worked. A bad argument gives you a
88
+ `ValueError` naming the fix, with no interruption to anyone.
89
+
90
+ ## Examples
91
+
92
+ Keep the ungated work out of the gated exec's blast radius:
93
+
94
+ ```python
95
+ # One exec: gather everything first, cheaply and without prompting.
96
+ hits = await api.grep(r"TODO", path="src", glob_filter="*.ts")
97
+ return hits
98
+ ```
99
+
100
+ ```python
101
+ # A second exec: now spend the user's attention, once, on the change itself.
102
+ r = await api.edit_file("src/app.ts", "// TODO: cache", "const cache = new Map();")
103
+ return r
104
+ ```
105
+
106
+ Treating a refusal as an instruction rather than an error:
107
+
108
+ ```python
109
+ try:
110
+ out = await api.bash("pnpm test")
111
+ except PermissionError:
112
+ return "You declined the test run — tell me if you'd rather run it yourself."
113
+ return out["stdout"][-2000:]
114
+ ```
115
+
116
+ ## Getting it wrong
117
+
118
+ **Batching ten gated calls into one exec.** Each one prompts separately, so the
119
+ user gets ten dialogs, and a refusal on the third throws away the seven below
120
+ it. Group ungated reads together; keep gated writes deliberate.
121
+
122
+ **Retrying a declined call.** A decline is the user's decision, not a transient
123
+ failure. Take the refusal, and any note attached to it, as an instruction.
124
+
125
+ **Assuming permission was the failure.** `edit_file` returns `Error:` strings
126
+ for its own failures and only raises for a decline — see `man('edit_file')`. A
127
+ `PermissionError` means the user said no; an `Error:` return means the edit did
128
+ not match.
129
+
130
+ **Expecting a prompt in a harness.** A harness runs unattended and never
131
+ prompts. Gated calls there are decided by its grants: auto-allowed or
132
+ auto-denied, with no human in the loop. `ask_user` is refused outright. See
133
+ `man('harness')`.
134
+
135
+ ## See also
136
+
137
+ `sandbox`, `edit_file`, `bash`, `harness`, `ask_user`