@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.
- package/LICENSE.txt +49 -0
- package/README.md +33 -2
- package/ThirdPartyNotices.txt +4685 -0
- package/package.json +30 -4
- package/provider-clis.json +1211 -0
- package/python/XACTOR.md +146 -0
- package/python/browser_actor_host.py +456 -0
- package/python/cdp_page.py +363 -0
- package/python/harness_check.py +77 -0
- package/python/requirements.txt +1 -0
- package/python/test_line_endings.py +185 -0
- package/python/xactor/__init__.py +18 -0
- package/python/xactor/__main__.py +226 -0
- package/python/xactor/api.py +882 -0
- package/python/xactor/bridge.py +784 -0
- package/python/xactor/dom.py +117 -0
- package/python/xactor/man/add_card.md +89 -0
- package/python/xactor/man/agent-vs-fork.md +108 -0
- package/python/xactor/man/agent.md +333 -0
- package/python/xactor/man/ask_model.md +110 -0
- package/python/xactor/man/ask_reference.md +56 -0
- package/python/xactor/man/ask_user.md +103 -0
- package/python/xactor/man/attach_app.md +595 -0
- package/python/xactor/man/attach_ide.md +65 -0
- package/python/xactor/man/attach_ide_surface.md +31 -0
- package/python/xactor/man/await_user.md +214 -0
- package/python/xactor/man/bash.md +142 -0
- package/python/xactor/man/browser-driver.md +137 -0
- package/python/xactor/man/browser_activity.md +53 -0
- package/python/xactor/man/browser_attach.md +84 -0
- package/python/xactor/man/browser_close_tab.md +50 -0
- package/python/xactor/man/browser_detach.md +50 -0
- package/python/xactor/man/browser_exec.md +66 -0
- package/python/xactor/man/browser_focus_tab.md +47 -0
- package/python/xactor/man/browser_hybrid.md +47 -0
- package/python/xactor/man/browser_open.md +50 -0
- package/python/xactor/man/browser_open_tab.md +58 -0
- package/python/xactor/man/browser_plumbing.md +107 -0
- package/python/xactor/man/browser_screenshot.md +56 -0
- package/python/xactor/man/browser_snapshot.md +52 -0
- package/python/xactor/man/browser_tabs.md +67 -0
- package/python/xactor/man/bulletin.md +88 -0
- package/python/xactor/man/capture_page.md +54 -0
- package/python/xactor/man/cd.md +64 -0
- package/python/xactor/man/chess_move.md +53 -0
- package/python/xactor/man/children.md +89 -0
- package/python/xactor/man/close_task.md +14 -0
- package/python/xactor/man/compact.md +75 -0
- package/python/xactor/man/drive.md +140 -0
- package/python/xactor/man/drive_space.md +72 -0
- package/python/xactor/man/driving.md +168 -0
- package/python/xactor/man/edit_file.md +112 -0
- package/python/xactor/man/finding.md +194 -0
- package/python/xactor/man/fork_agent.md +45 -0
- package/python/xactor/man/glob.md +83 -0
- package/python/xactor/man/grep.md +115 -0
- package/python/xactor/man/harness.md +165 -0
- package/python/xactor/man/harness_check.md +70 -0
- package/python/xactor/man/harness_format.md +257 -0
- package/python/xactor/man/highlight.md +66 -0
- package/python/xactor/man/instruct.md +111 -0
- package/python/xactor/man/ls.md +63 -0
- package/python/xactor/man/man.md +86 -0
- package/python/xactor/man/merge_wiz.md +40 -0
- package/python/xactor/man/notebook.md +98 -0
- package/python/xactor/man/overview.md +89 -0
- package/python/xactor/man/permissions.md +137 -0
- package/python/xactor/man/phase.md +150 -0
- package/python/xactor/man/powershell.md +74 -0
- package/python/xactor/man/profiles.md +60 -0
- package/python/xactor/man/project_drive.md +49 -0
- package/python/xactor/man/read_bulletin.md +89 -0
- package/python/xactor/man/read_file.md +90 -0
- package/python/xactor/man/relay.md +216 -0
- package/python/xactor/man/reply.md +72 -0
- package/python/xactor/man/reply_message.md +78 -0
- package/python/xactor/man/report_bug.md +118 -0
- package/python/xactor/man/request_answer.md +142 -0
- package/python/xactor/man/request_merge_decisions.md +121 -0
- package/python/xactor/man/request_operator_action.md +152 -0
- package/python/xactor/man/request_review.md +122 -0
- package/python/xactor/man/request_user_action.md +126 -0
- package/python/xactor/man/return_card.md +71 -0
- package/python/xactor/man/run.md +99 -0
- package/python/xactor/man/sandbox.md +144 -0
- package/python/xactor/man/session_fork.md +74 -0
- package/python/xactor/man/session_harness.md +109 -0
- package/python/xactor/man/session_join.md +102 -0
- package/python/xactor/man/session_land.md +73 -0
- package/python/xactor/man/session_nudge.md +73 -0
- package/python/xactor/man/session_rest.md +73 -0
- package/python/xactor/man/session_scope.md +84 -0
- package/python/xactor/man/session_terminate.md +58 -0
- package/python/xactor/man/session_token.md +78 -0
- package/python/xactor/man/sessions.md +93 -0
- package/python/xactor/man/set_harness.md +15 -0
- package/python/xactor/man/spawn_swarm.md +80 -0
- package/python/xactor/man/submit_evidence.md +54 -0
- package/python/xactor/man/swarm_brief.md +30 -0
- package/python/xactor/man/task_status.md +82 -0
- package/python/xactor/man/tasks.md +47 -0
- package/python/xactor/man/todo_write.md +104 -0
- package/python/xactor/man/update_card.md +70 -0
- package/python/xactor/man/use_profile.md +90 -0
- package/python/xactor/man/use_widget.md +82 -0
- package/python/xactor/man/wait_for_task.md +105 -0
- package/python/xactor/man/widget-model.md +284 -0
- package/python/xactor/man/widgets.md +59 -0
- package/python/xactor/man/write_file.md +76 -0
- package/python/xactor/man/writing-a-widget.md +142 -0
- package/python/xactor/man/xylon.md +120 -0
- package/python/xactor/man/xylon_blame.md +80 -0
- package/python/xactor/man/xylon_read.md +131 -0
- package/python/xactor/man/xylon_search.md +125 -0
- package/python/xactor/manual.py +195 -0
- package/python/xactor/parent_watch.py +115 -0
- package/python/xactor/plan_mode.py +350 -0
- package/python/xactor/sandbox.py +856 -0
- package/python/xactor/seals.py +438 -0
- package/python/xactor/shell_hints.py +210 -0
- package/python/xactor/states/__init__.py +13 -0
- package/python/xactor/states/base.py +523 -0
- package/python/xactor/states/registry.py +274 -0
- package/python/xactor/stdlib_api.py +6345 -0
- package/python/xactor/widgets/__init__.py +1 -0
- package/python/xactor/widgets/github_tokens.py +314 -0
- package/python/xactor/widgets/google_aistudio.py +365 -0
- package/settings-schema.json +403 -0
- package/viewer/index.css +160 -0
- package/viewer/index.js +15 -0
- 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`
|