@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,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`