@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,47 @@
|
|
|
1
|
+
# api.tasks() -> list[dict]
|
|
2
|
+
|
|
3
|
+
<!-- group: Sessions -->
|
|
4
|
+
|
|
5
|
+
Every task, and the agents working in it. Never asks.
|
|
6
|
+
|
|
7
|
+
A task is a worktree on its own branch with a title. Its agents are the sessions
|
|
8
|
+
working in it. The Coordinator uses this to answer "what is going on" and to
|
|
9
|
+
choose where new work goes.
|
|
10
|
+
|
|
11
|
+
## Returns
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
[{"id": "issue-7", "title": "Markdown replies", "branch": "widgex/markdown-replies",
|
|
15
|
+
"main": False, "dismissed": False, "ahead": 3, "dirty": 0,
|
|
16
|
+
"lead": "session-12",
|
|
17
|
+
"agents": [{"id": "session-12", "label": "Tests", "status": "working",
|
|
18
|
+
"harness": {"name": "tdd", "state": "red"} or None,
|
|
19
|
+
"asked": "Write tests for …", "latest": "Added 22 cases …", "files": 4,
|
|
20
|
+
"lead": True, "node": "N1/A4"}]}]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`main` is the entry for agents running in the folder you have open. `latest` is
|
|
24
|
+
the last thing the agent said, trimmed. `dismissed` tasks are hidden from the
|
|
25
|
+
header but still running.
|
|
26
|
+
|
|
27
|
+
## The lead
|
|
28
|
+
|
|
29
|
+
A task has **one lead** — the first agent spun up in it — and any number of aux
|
|
30
|
+
agents helping. `lead` on the task is that agent's session id, and `lead` on an
|
|
31
|
+
agent says which one it is. `node` is the address to reach an agent at, which is
|
|
32
|
+
what `relay_send` takes; a session id is not.
|
|
33
|
+
|
|
34
|
+
The lead is who to ask how a task is going, and the state the task shows is
|
|
35
|
+
*its* state — an aux agent still running does not make a finished task read as
|
|
36
|
+
running. The one exception is an agent blocked on a permission, which shows on
|
|
37
|
+
the task whoever it is, because that is the state somebody has to answer.
|
|
38
|
+
|
|
39
|
+
Close the lead and the next-oldest agent becomes the lead. Nothing records it;
|
|
40
|
+
it is the first agent still standing in the task, so it follows the task rather
|
|
41
|
+
than having to be maintained.
|
|
42
|
+
|
|
43
|
+
`None` on the main checkout, which is not a task — the agents in the folder you
|
|
44
|
+
have open answer to nobody in particular.
|
|
45
|
+
|
|
46
|
+
Aux agents are told all of this when they start, and told to report to their
|
|
47
|
+
lead by relay. You do not have to repeat it in a brief.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# api.todo_write(todos) -> dict
|
|
2
|
+
<!-- group: Talking to the user -->
|
|
3
|
+
|
|
4
|
+
Publish the task list shown in chat. **Replaces the whole list.** Not gated.
|
|
5
|
+
|
|
6
|
+
This is the user's view of what you are doing and how far along you are. It is
|
|
7
|
+
destructive and silent: whatever you send becomes the entire list, and nothing
|
|
8
|
+
warns you that you dropped an item.
|
|
9
|
+
|
|
10
|
+
**This list is yours, and it is ephemeral** — it lives in the chat and is gone
|
|
11
|
+
on a window reload. Work you want somebody to pick up *later* goes on the user's
|
|
12
|
+
board instead, with [`add_card`](add_card.md), which is durable and is not
|
|
13
|
+
replaced by the next call.
|
|
14
|
+
|
|
15
|
+
## Options
|
|
16
|
+
|
|
17
|
+
- **`todos`** — a list of dicts, each with:
|
|
18
|
+
- **`content`** — imperative form, what needs doing: `"Run the tests"`.
|
|
19
|
+
Required and non-empty.
|
|
20
|
+
- **`activeForm`** — present continuous, shown while it runs:
|
|
21
|
+
`"Running the tests"`. Defaults to `content` if omitted.
|
|
22
|
+
- **`status`** — `"pending"`, `"in_progress"` or `"completed"`. Defaults to
|
|
23
|
+
`"pending"`. Anything else is a `ValueError`.
|
|
24
|
+
|
|
25
|
+
**Send every item every time, with its current status.** Keep exactly one item
|
|
26
|
+
`in_progress`. Mark work `completed` the moment it is done, not in a batch at
|
|
27
|
+
the end.
|
|
28
|
+
|
|
29
|
+
## Returns
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
{"count": int} # how many items are now displayed
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
Publishing the plan:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
return await api.todo_write([
|
|
41
|
+
{"content": "Read the existing auth flow",
|
|
42
|
+
"activeForm": "Reading the existing auth flow", "status": "in_progress"},
|
|
43
|
+
{"content": "Add the refresh-token endpoint",
|
|
44
|
+
"activeForm": "Adding the refresh-token endpoint", "status": "pending"},
|
|
45
|
+
{"content": "Write tests for expiry",
|
|
46
|
+
"activeForm": "Writing tests for expiry", "status": "pending"},
|
|
47
|
+
])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Advancing it — note that the completed item is still in the list:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
return await api.todo_write([
|
|
54
|
+
{"content": "Read the existing auth flow",
|
|
55
|
+
"activeForm": "Reading the existing auth flow", "status": "completed"},
|
|
56
|
+
{"content": "Add the refresh-token endpoint",
|
|
57
|
+
"activeForm": "Adding the refresh-token endpoint", "status": "in_progress"},
|
|
58
|
+
{"content": "Write tests for expiry",
|
|
59
|
+
"activeForm": "Writing tests for expiry", "status": "pending"},
|
|
60
|
+
])
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Keeping the list in a variable so you cannot lose an item:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
plan = [
|
|
67
|
+
{"content": "Read the existing auth flow", "activeForm": "Reading the existing auth flow"},
|
|
68
|
+
{"content": "Add the refresh-token endpoint", "activeForm": "Adding the refresh-token endpoint"},
|
|
69
|
+
{"content": "Write tests for expiry", "activeForm": "Writing tests for expiry"},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
def at(i):
|
|
73
|
+
return [{**t, "status": "completed" if n < i else "in_progress" if n == i else "pending"}
|
|
74
|
+
for n, t in enumerate(plan)]
|
|
75
|
+
|
|
76
|
+
return await api.todo_write(at(1))
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Getting it wrong
|
|
80
|
+
|
|
81
|
+
**Sending only what changed.**
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# Wrong — the list is now one item long and the other four are gone.
|
|
85
|
+
await api.todo_write([{"content": "Add the endpoint", "status": "in_progress"}])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
There is no patch, no append and no merge. Send the whole list.
|
|
89
|
+
|
|
90
|
+
**More than one `in_progress`, or none.** The list is meant to show what you
|
|
91
|
+
are doing *now*. Exactly one.
|
|
92
|
+
|
|
93
|
+
**Batching completions.** Marking five items done at the end tells the user
|
|
94
|
+
nothing while the work happens, which is the only time the list is useful.
|
|
95
|
+
|
|
96
|
+
**Using it for a single trivial task.** A list with one item on it is noise.
|
|
97
|
+
Just do the thing.
|
|
98
|
+
|
|
99
|
+
**Omitting `activeForm`.** It falls back to `content`, so the user reads "Run
|
|
100
|
+
the tests" while the tests are running. Cheap to get right.
|
|
101
|
+
|
|
102
|
+
## See also
|
|
103
|
+
|
|
104
|
+
`add_card`, `ask_user`, `sessions`, `agent`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# api.update_card(card_id, lane=None, session_id=None, title=None, prompt=None, withdraw=False) -> dict
|
|
2
|
+
<!-- also: move_card, card_lane, attach_card, mirror_card, board_mirror, kanban_move, edit_card, remove_card, delete_card, withdraw_card -->
|
|
3
|
+
<!-- group: Talking to the user -->
|
|
4
|
+
|
|
5
|
+
Say what a card is now: which lane it is in, and whose work it is. Refused in
|
|
6
|
+
Plan mode.
|
|
7
|
+
|
|
8
|
+
## The board is a picture, not a launcher
|
|
9
|
+
|
|
10
|
+
Dropping a card into Doing used to cut a branch, make a session and prompt it.
|
|
11
|
+
It does not any more — unattended work starts in one place now, the orchestrator
|
|
12
|
+
— so a card only stands for something running if you say so.
|
|
13
|
+
|
|
14
|
+
That is what this is for. You dispatch a subagent, then point a card at it:
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
child = await api.agent(step.prompt, isolate=True, wait=False, must=step.must)
|
|
18
|
+
await api.update_card(card.id, lane="doing", session_id=child["session_id"])
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Pointing a card at a session turns the rest of the board on
|
|
22
|
+
|
|
23
|
+
A card with a session is a different object from a card with text on it. It is
|
|
24
|
+
the one that moves itself to **Blocked** when its agent asks a human something,
|
|
25
|
+
that the landing sweep can **integrate** when its branch merges, and that grows
|
|
26
|
+
a badge when its agent stops early. A card with no session is a note.
|
|
27
|
+
|
|
28
|
+
You do not pass the branch or the base commit. They are read off the session,
|
|
29
|
+
which is the thing that cannot be wrong about them — a card claiming a branch
|
|
30
|
+
its session is not on is a card that offers to land the wrong work.
|
|
31
|
+
|
|
32
|
+
## Lanes
|
|
33
|
+
|
|
34
|
+
`todo`, `planning`, `doing` and `done` are yours to set. The other two are
|
|
35
|
+
claims only the world can make: **Blocked** is entered by an agent asking a human
|
|
36
|
+
to look, answer or allow something, and **Integrated** by a branch actually
|
|
37
|
+
landing. Naming one of those is
|
|
38
|
+
refused rather than quietly ignored — a board saying work has shipped when it
|
|
39
|
+
has not is worse than no board.
|
|
40
|
+
|
|
41
|
+
## Your own descendants only
|
|
42
|
+
|
|
43
|
+
A card may only point at a session underneath you. Pointing one at somebody
|
|
44
|
+
else's would put another agent's work on the user's board and — worse — offer
|
|
45
|
+
them a Retry button that prompts a session you do not own.
|
|
46
|
+
|
|
47
|
+
## Fixing or taking back a card you wrote
|
|
48
|
+
|
|
49
|
+
A card you added with `add_card` that went up wrong can be put right, or
|
|
50
|
+
removed, while it is untouched:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
await api.update_card(card["id"], prompt="Rename X to Y in Z.", title="Rename X")
|
|
54
|
+
await api.update_card(card["id"], withdraw=True)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Untouched means the user has not opened it — opening a card clears the mark
|
|
58
|
+
that says you wrote it — and nothing has been started from it. After that the
|
|
59
|
+
card is theirs, and both are refused: ask them in the conversation instead. A
|
|
60
|
+
card somebody else wrote is never yours to change.
|
|
61
|
+
|
|
62
|
+
Build a prompt from your own words, not from `read_file` output: that carries
|
|
63
|
+
line-number prefixes, and they go onto the card verbatim.
|
|
64
|
+
|
|
65
|
+
## It is not `api.add_card`
|
|
66
|
+
|
|
67
|
+
`add_card` makes a new card, in Todo, for work nobody is doing. This changes one
|
|
68
|
+
that exists. Both are the user's board and both are durable; neither is
|
|
69
|
+
`api.todo_write`, which is your own progress list in the chat and is gone on a
|
|
70
|
+
reload.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# api.use_profile(name) -> dict
|
|
2
|
+
<!-- group: Working on a website -->
|
|
3
|
+
|
|
4
|
+
Browse as a different identity for the rest of this session. Gated.
|
|
5
|
+
|
|
6
|
+
A **profile** is a whole browser with its own cookies. Switching is not a Google
|
|
7
|
+
account switch — it is a different logged-in identity on *every* site, which is
|
|
8
|
+
the thing a single cookie jar cannot give you.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
WidgeX's own browser is VS Code's Electron, and Google's consumer sign-in
|
|
13
|
+
refuses it outright — *"This browser or app may not be secure"*. That refusal is
|
|
14
|
+
not about credentials: it fires on account selection, before any password is
|
|
15
|
+
asked for, and a Workspace account whose organisation uses SSO signs in fine in
|
|
16
|
+
the very same browser because Google never collects the password and so never
|
|
17
|
+
runs the check.
|
|
18
|
+
|
|
19
|
+
So a profile is a *real* Chrome, launched and owned by WidgeX, one per identity.
|
|
20
|
+
Nothing about it is disguised, which is why nothing about it breaks when the far
|
|
21
|
+
end changes what it inspects.
|
|
22
|
+
|
|
23
|
+
## Options
|
|
24
|
+
|
|
25
|
+
- **`name`** — the profile's name as `api.profiles()` reports it, matched
|
|
26
|
+
case-insensitively. Its id is accepted too, so a name read out of `profiles()`
|
|
27
|
+
can be passed straight back.
|
|
28
|
+
|
|
29
|
+
## Returns
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
{"profile": {"id": str, "name": str}, "port": int}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The browser is started as part of this call, so by the time it returns there is
|
|
36
|
+
something to drive.
|
|
37
|
+
|
|
38
|
+
## Scoped to your session
|
|
39
|
+
|
|
40
|
+
Another session working beside you keeps whatever identity it had. You cannot
|
|
41
|
+
change who it is and it cannot change who you are — two agents can hold two
|
|
42
|
+
identities at the same time, which is the point of putting this on the session
|
|
43
|
+
rather than on the window.
|
|
44
|
+
|
|
45
|
+
## Examples
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
print(await api.profiles())
|
|
49
|
+
await api.use_profile("personal")
|
|
50
|
+
await api.drive("https://mail.google.com", why="read the thread the user linked")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Signing in
|
|
54
|
+
|
|
55
|
+
A profile nobody has signed in to yet is **not an error**. You arrive at a
|
|
56
|
+
logged-out page, and the way through is to hand it to the person:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
await api.use_profile("personal")
|
|
60
|
+
await api.drive("https://accounts.google.com/", why="let the user sign this profile in")
|
|
61
|
+
await api.request_operator_action(
|
|
62
|
+
"Sign In",
|
|
63
|
+
detail="Sign in to Google in this profile. The session is kept, so this is once.",
|
|
64
|
+
timeout_seconds=600, # and raise the exec's own timeout past this
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Never type a password yourself.** That is what `request_operator_action` is
|
|
69
|
+
for, and it mirrors the tab into the chat so the person never leaves WidgeX.
|
|
70
|
+
|
|
71
|
+
## Getting it wrong
|
|
72
|
+
|
|
73
|
+
**Expecting it to switch Google accounts within a profile.** It switches the
|
|
74
|
+
whole browser. Several Google accounts inside *one* profile is Google's own
|
|
75
|
+
"Add account", and they share that profile's cookies.
|
|
76
|
+
|
|
77
|
+
**Calling it per navigation.** Identity is a mode, not an argument. `drive` has
|
|
78
|
+
no `profile=` parameter on purpose: a navigation that silently changed who you
|
|
79
|
+
are is exactly the thing worth making explicit.
|
|
80
|
+
|
|
81
|
+
**Assuming the picker and your session agree.** `profiles()` reports `active`
|
|
82
|
+
(what a human chose in the widget) and `current` (what *this session* is using)
|
|
83
|
+
separately, because they are allowed to differ.
|
|
84
|
+
|
|
85
|
+
**Deleting a profile to "reset" it.** That discards its cookies, and with them
|
|
86
|
+
the sign-in. It also refuses while its browser is open.
|
|
87
|
+
|
|
88
|
+
## See also
|
|
89
|
+
|
|
90
|
+
`profiles`, `drive`, `request_operator_action`, `use_widget`
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# api.use_widget(name, state=None) -> dict
|
|
2
|
+
<!-- group: Working on a website -->
|
|
3
|
+
|
|
4
|
+
Work on a site through its widget. Gated.
|
|
5
|
+
|
|
6
|
+
Ensures a browser, loads the widget's page-state machine into the registry, and
|
|
7
|
+
navigates to its entry point. What the user approves — and what the panel shows
|
|
8
|
+
them — is the widget: a named automation someone wrote and anyone can read.
|
|
9
|
+
|
|
10
|
+
This is the normal way to reach a website.
|
|
11
|
+
|
|
12
|
+
## Options
|
|
13
|
+
|
|
14
|
+
- **`name`** — a widget name from `api.widgets()`. A non-string or an empty
|
|
15
|
+
string raises before anything opens.
|
|
16
|
+
- **`state`** — a `PageState` name to enter. Defaults to the widget's home. A
|
|
17
|
+
non-string raises.
|
|
18
|
+
|
|
19
|
+
## Returns
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
{"widget": str, "state": str, "url": str, "states": {...}}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`states` is the loaded machine — the state names and their shapes, so you can
|
|
26
|
+
see what the widget models without opening it.
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
The standard shape of a browser task:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
await api.use_widget("vercel_tokens")
|
|
34
|
+
state = await registry.here(api)
|
|
35
|
+
print(state.describe())
|
|
36
|
+
state = await state.execute("create_token", token_name="ci-deploy")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`registry` and the widget classes are bound in this shell, so the machine is
|
|
40
|
+
something you call directly — there is nothing to wrap it in. See `driving`.
|
|
41
|
+
|
|
42
|
+
Entering somewhere other than home:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
return await api.use_widget("github", state="PullRequest")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Picking a widget from a URL:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
found = await api.widgets("https://vercel.com/account/tokens")
|
|
52
|
+
match = next((w for w in found if w["matches"]), None)
|
|
53
|
+
if match is None:
|
|
54
|
+
return "nothing models that page"
|
|
55
|
+
return await api.use_widget(match["name"])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Getting it wrong
|
|
59
|
+
|
|
60
|
+
**Guessing the name.** `api.widgets()` is free and lists them. A wrong name
|
|
61
|
+
raises after the user has already been prompted.
|
|
62
|
+
|
|
63
|
+
**Expecting to be able to act immediately.** This lands you on a page. The
|
|
64
|
+
actions live on the state — `registry.here(api)`, then `state.describe()`, then
|
|
65
|
+
`state.execute(...)`, right here in this shell.
|
|
66
|
+
|
|
67
|
+
**Wrapping those in `browser_exec`.** A common misread, because examples written
|
|
68
|
+
before the shells merged did. `browser_exec` is for *another session's* browser.
|
|
69
|
+
|
|
70
|
+
**Using it for a site with no widget.** That is `api.drive`, and it needs a
|
|
71
|
+
`why`.
|
|
72
|
+
|
|
73
|
+
**Assuming the entry state is where you ended up.** A redirect or a login wall
|
|
74
|
+
can land you elsewhere. `registry.here()` tells you the truth; the return value
|
|
75
|
+
tells you the intent.
|
|
76
|
+
|
|
77
|
+
**Reaching for raw clicks once loaded.** The widget's transitions are the point.
|
|
78
|
+
`describe()` lists them.
|
|
79
|
+
|
|
80
|
+
## See also
|
|
81
|
+
|
|
82
|
+
`driving`, `widgets`, `drive`, `widget-model`, `browser_plumbing`
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# api.wait_for_task(node, n=None, timeout=None) -> dict
|
|
2
|
+
<!-- group: Talking to other agents -->
|
|
3
|
+
|
|
4
|
+
Block until another agent finishes one task, or its whole list. Not gated.
|
|
5
|
+
|
|
6
|
+
Reads positions off the same board `read_bulletin` shows. Use it when your work
|
|
7
|
+
genuinely cannot begin until theirs lands — not to keep tabs on somebody.
|
|
8
|
+
|
|
9
|
+
## Options
|
|
10
|
+
|
|
11
|
+
- **`node`** — the agent, named as it appears on the bulletin: `"N1"`,
|
|
12
|
+
`"N1/A11/E1"`. `"."` is you and `".."` is your parent. The workspace segment
|
|
13
|
+
is optional; `"N1"` and `"widgex/N1"` are the same agent.
|
|
14
|
+
- **`n`** — the 1-based task position, read from `read_bulletin`. Omit it to
|
|
15
|
+
wait until that agent's **entire list** is done and it leaves the board.
|
|
16
|
+
- **`timeout`** — seconds. Must satisfy `0 < timeout <= 3600`. Defaults to
|
|
17
|
+
**300**.
|
|
18
|
+
|
|
19
|
+
**Raise the exec's own `timeout_seconds` above this**, or the tool call running
|
|
20
|
+
your code is killed while the wait is still going. The tool call defaults to
|
|
21
|
+
120 seconds, which is shorter than this call's own default of 300. This is the
|
|
22
|
+
single most common way to get this call wrong.
|
|
23
|
+
|
|
24
|
+
## What is actually waited on
|
|
25
|
+
|
|
26
|
+
The task's **text**, snapshotted when you call. Not its position.
|
|
27
|
+
|
|
28
|
+
`todo_write` replaces the whole list every time, so positions move constantly —
|
|
29
|
+
an agent that inserts one item ahead of yours would otherwise silently move you
|
|
30
|
+
onto a different task. Waiting on the text survives reordering, and tells you
|
|
31
|
+
plainly when the task was abandoned instead.
|
|
32
|
+
|
|
33
|
+
## Returns
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
{"status": str, "node": str, "task": str | None, "index": int | None, "waitedMs": int}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| `status` | Meaning |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `completed` | The task reached `completed`. With `n=None`, the whole list did. |
|
|
42
|
+
| `timeout` | The deadline passed. They are still working; nothing is broken. |
|
|
43
|
+
| `gone` | They dropped it. `reason` is `agent-left` or `task-dropped`. |
|
|
44
|
+
| `no-such-agent` | Nothing on the board matches. `agents` lists who is on it. |
|
|
45
|
+
| `no-such-task` | `n` is past the end of their list. `count` says how long it is. |
|
|
46
|
+
|
|
47
|
+
**All five are returns.** Waiting on yourself and giving an unresolvable
|
|
48
|
+
relative ref **raise** — the first would deadlock and the second is a typo, and
|
|
49
|
+
neither is something to route around.
|
|
50
|
+
|
|
51
|
+
`no-such-agent` is usually not an error at all: an agent that finished
|
|
52
|
+
everything has already left the board.
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
55
|
+
|
|
56
|
+
Wait for one task, with the exec timeout raised to match:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# timeout_seconds=400 on the tool call
|
|
60
|
+
r = await api.wait_for_task("N1", 2, timeout=300)
|
|
61
|
+
if r["status"] != "completed":
|
|
62
|
+
return f"Not waiting further — {r['node']} task 2 came back {r['status']}."
|
|
63
|
+
return "Their migration landed; starting mine."
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Read the board first, so the number means something:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
board = await api.read_bulletin()
|
|
70
|
+
# ... pick the agent and the position out of it ...
|
|
71
|
+
return await api.wait_for_task("N2/E1", 3, timeout=600)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Wait for an agent to finish everything:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
r = await api.wait_for_task("N1", timeout=900)
|
|
78
|
+
return r["status"] # 'completed' once it leaves the board
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Getting it wrong
|
|
82
|
+
|
|
83
|
+
**Leaving the exec timeout at its default.** `timeout=300` inside a tool call
|
|
84
|
+
capped at 120 seconds means your code is killed at 120 seconds and you learn
|
|
85
|
+
nothing. Set the tool call's `timeout_seconds` higher than this call's.
|
|
86
|
+
|
|
87
|
+
**Not checking `status`.** Only `completed` means the work happened. `timeout`
|
|
88
|
+
and `gone` both come back with a perfectly ordinary-looking dict.
|
|
89
|
+
|
|
90
|
+
**Treating `no-such-agent` as a failure.** It usually means they finished.
|
|
91
|
+
Check the `agents` list it returns before concluding you got the name wrong.
|
|
92
|
+
|
|
93
|
+
**Waiting on work you could just do.** A wait costs you the whole timeout and
|
|
94
|
+
costs them nothing. If you can do the thing yourself, do it.
|
|
95
|
+
|
|
96
|
+
**Waiting on each other.** Two agents can wait on each other and both sit until
|
|
97
|
+
their timeouts. Nothing detects this — the timeout is the only thing that ends
|
|
98
|
+
it. Waiting on yourself is caught and raises.
|
|
99
|
+
|
|
100
|
+
**Polling `read_bulletin` instead.** That is what this call is for, and a loop
|
|
101
|
+
finds out no sooner.
|
|
102
|
+
|
|
103
|
+
## See also
|
|
104
|
+
|
|
105
|
+
`read_bulletin`, `bulletin`, `todo_write`, `session_join`, `relay`
|