@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,121 @@
1
+ # api.request_merge_decisions(source_branch, target_ref, ambiguities, resolved=None, timeout_seconds=3600) -> dict
2
+
3
+ <!-- also: merge_decisions, merge_board, resolve_merge, merge_conflicts, conflicts -->
4
+ <!-- group: Talking to the user -->
5
+
6
+ Ask the user which features to keep, where a merge is genuinely ambiguous.
7
+
8
+ You are standing in a working copy with a conflicted rebase in it. Most of what
9
+ git stopped on, you can settle: an import both sides added, a rename on one side
10
+ against an edit on the other, two formattings of the same block. Some of it you
11
+ cannot, because both sides implemented the same thing differently and keeping
12
+ the wrong one silently loses somebody's work.
13
+
14
+ This is for that second kind. The user gets a board — your branch's features
15
+ down the left, the target's down the right, and an **include** column in the
16
+ middle they drag into.
17
+
18
+ **It blocks**, and the default timeout is an hour. They are reading a diff's
19
+ worth of consequence, not dismissing a dialog.
20
+
21
+ ## The shape that matters
22
+
23
+ ```python
24
+ r = await api.request_merge_decisions(
25
+ source_branch="widgex/merge_flow_fix",
26
+ target_ref="origin/main",
27
+ resolved=[
28
+ {"path": "src/config.ts",
29
+ "summary": "Both added an unrelated field; kept both."},
30
+ ],
31
+ ambiguities=[{
32
+ "id": "discovery",
33
+ "question": "How model discovery decides a runtime is reachable.",
34
+ "choices": [
35
+ {"id": "discovery-branch", "side": "branch",
36
+ "title": "Ollama discovery over fetch",
37
+ "detail": "Probes with a fetch stub, so the suite needs no socket. "
38
+ "Loses the shared timeout the target side applies.",
39
+ "paths": ["src/providers/ollamaDiscovery.ts"]},
40
+ {"id": "discovery-target", "side": "target",
41
+ "title": "Discovery through the shared probe",
42
+ "detail": "Routes every runtime through one prober, so timeouts are "
43
+ "uniform. Opens a real socket, which CI has no route for.",
44
+ "paths": ["src/providers/ollamaDiscovery.ts", "src/providers/probe.ts"]},
45
+ ],
46
+ }],
47
+ timeout_seconds=3600,
48
+ )
49
+
50
+ if r["outcome"] == "submitted":
51
+ print(r["instructions"]) # ← act on this
52
+ ```
53
+
54
+ Run it with `widgex_terminal(code=..., timeout_seconds=3900)`. The tool's clock
55
+ and the exec's are separate, and both have to outlast the person.
56
+
57
+ ## Ask about as little as possible
58
+
59
+ A card is a **fork in the road**, not a conflict marker. If you can work out the
60
+ right answer, that is not an ambiguity — resolve it, list it under `resolved`,
61
+ and do not put it on the board.
62
+
63
+ Calling this with an empty `ambiguities` is refused, and the refusal says to
64
+ continue rather than to retry, because at that point you are finished.
65
+
66
+ The counter-failure is worse, though, and it is the one to watch for: settling
67
+ something you should have asked about. Two implementations of one feature, a
68
+ behaviour the user chose deliberately on one side, anything where you find
69
+ yourself reasoning about what they *probably* wanted — that is a card.
70
+
71
+ ## They can include both, and usually will
72
+
73
+ Two cards in the middle is not a contradiction for you to resolve by picking
74
+ one. It is an instruction to **weave both features together** in the resolved
75
+ file. Order in the middle column is precedence, for wherever they genuinely
76
+ collide on the same lines.
77
+
78
+ `instructions` says this in words. Read it rather than inferring from
79
+ `decisions`, which carries the same answer as bare choice ids.
80
+
81
+ ## Options
82
+
83
+ - **`source_branch`** — the branch being replayed. `"widgex/merge_flow_fix"`.
84
+ - **`target_ref`** — what it is being replayed onto. `"origin/main"`.
85
+ - **`ambiguities`** — one per fork in the road:
86
+ - **`id`** — unique across the board.
87
+ - **`question`** — one line on what is actually in doubt.
88
+ - **`choices`** — at least two. One choice is not a question.
89
+ - **`id`** — unique within its ambiguity. This is what a drop carries.
90
+ - **`side`** — `"branch"` or `"target"`, which column it starts in.
91
+ - **`title`** — the feature, named. "Ollama model discovery".
92
+ - **`detail`** — what this side does here **and how it differs from the
93
+ other side**. This is the entire reason a card beats a merge editor.
94
+ "Keeps the old behaviour" tells them nothing.
95
+ - **`paths`** — files it touches. Never empty.
96
+ - **`resolved`** — `[{"path", "summary"}]` for what you settled alone. Shown
97
+ read-only at the bottom of the board. A merge that silently decided seven
98
+ things and displayed none of them is one nobody can audit.
99
+ - **`timeout_seconds`** — how long to wait.
100
+
101
+ ## What comes back
102
+
103
+ - **`submitted`** — they answered. `instructions` is prose naming each feature
104
+ they included, which branch it came from, its files, and the precedence order.
105
+ `decisions` is the same as raw ids.
106
+ - **`asked`** — they want something from you before they can decide, in `note`.
107
+ Answer it and ask again.
108
+ - **`declined`** — they would rather do it by hand. Leave the conflict markers
109
+ alone and say so.
110
+
111
+ `ValueError` means the board was malformed and the message says what to fix.
112
+ Repair it and call again — do **not** fall back to resolving the ambiguity
113
+ yourself, which is the one thing the user asked to decide.
114
+
115
+ `RuntimeError` means nobody answered in time. The markers are untouched.
116
+
117
+ ## Related
118
+
119
+ `phase_complete` hands over a judgement about work already done;
120
+ `request_user_action` hands over work you cannot do. This hands over a
121
+ **choice**, and is the only one of the three that comes back with a payload.
@@ -0,0 +1,152 @@
1
+ # api.request_operator_action(action, detail=None, timeout_seconds=600) -> dict
2
+ <!-- also: operator, operator-action, handoff, hand-over, verification, verify, does-it-work -->
3
+ <!-- group: Talking to the user -->
4
+
5
+ Hand the user a step on the page you are already on. **Does not block. Not
6
+ gated.**
7
+
8
+ `request_user_action` points at a `url` and trusts the user to go there. This is
9
+ about the page **your session is driving** — an app window or a browser tab
10
+ alike, with your cookies and your login state, and the step is one they take in
11
+ place.
12
+
13
+ ## It does not block, and that changed what "in place" means
14
+
15
+ Your turn finishes; the request waits as a row in the user's inbox.
16
+
17
+ It used to draw the card into the driven tab at the moment you asked, which was
18
+ only possible because you were standing there holding the page open. You are
19
+ not any more — so the card moves to where the user is, and **opening the row
20
+ wakes you** to bring the page back up and walk them through it with
21
+ `api.instruct` / `api.await_user`.
22
+
23
+ **When you are driving a browser tab, the card links to it.** The tab you are on
24
+ is handed to the user: its Open button brings that page up exactly as you left
25
+ it — a form you filled in stays filled in — and opening it wakes nobody, because
26
+ there is nothing for you to do until they press Done. You will not navigate that
27
+ tab again, and it stays open after you finish. So fill in everything you can
28
+ first, and hand over only the step that is theirs: Submit, a sign-in, a CAPTCHA.
29
+
30
+ With no tab — an attached app, say — the old behaviour holds, below.
31
+
32
+ That wake is now the whole difference between this verb and
33
+ `request_user_action`. A user action carries a url and is worth answering with
34
+ you long gone; this one is worth nothing without you, so opening it fetches you.
35
+ Pick accordingly: the wrong one here costs the user either a turn they did not
36
+ need or a row nobody can act on.
37
+
38
+ Say everything the next you will need in `detail`, and end the turn:
39
+
40
+ ```python
41
+ await api.drive("https://console.example.com/login", why="sign in to read the keys")
42
+ await api.request_operator_action(
43
+ "Sign In",
44
+ detail="Sign in with your own account. I'll read the API key names once "
45
+ "you're in — I won't see your password. Say so if you'd rather not.",
46
+ )
47
+ return "Handed you the console sign-in; nothing else needs doing until it's done."
48
+ ```
49
+
50
+ Writing the rest of the turn as though the step were already taken is the way
51
+ this goes wrong. **Look at the page** when you are woken — `done` means they
52
+ pressed Done, not that the thing you wanted happened.
53
+
54
+ ## When to use it
55
+
56
+ For what you cannot do and should not try to:
57
+
58
+ - signing in, including 2FA and passkeys
59
+ - clearing a CAPTCHA or a bot check
60
+ - choosing between accounts or organisations
61
+ - anything at all involving the user's own credentials
62
+
63
+ **Never type a password yourself.** Not from an env var, not from something you
64
+ found on disk. Hand the page over and let the person type it.
65
+
66
+ ### Asking whether something you built works
67
+
68
+ Outside a phase, this is also how to hand back a change somebody can see. Drive
69
+ to where it can be tried, then make `action` the thing to try and `detail` what
70
+ they should see:
71
+
72
+ ```python
73
+ await api.request_operator_action(
74
+ "Type a draft, reload, check it is still there",
75
+ detail="Drafts should now save on their own. Done if the text survives the "
76
+ "reload; Can't do this, with what you saw, if it does not.",
77
+ )
78
+ ```
79
+
80
+ **declined** with a note is the answer you asked for, not a failure: read it,
81
+ fix what it says, and ask again. Inside a phase, the lead asks with
82
+ [`phase_complete`](phase.md) instead — one review card for the whole phase.
83
+
84
+ ## Options
85
+
86
+ - **`action`** — the label on the button. Imperative and short: `"Sign In"`.
87
+ Over 60 characters is rejected; the explanation goes in `detail`.
88
+ - **`detail`** — one line on what to do and what happens next. The user is
89
+ choosing between finishing, giving up and asking you something, so say what
90
+ each will mean — and say it for a reader who was not here, because this is all
91
+ the next you will have.
92
+ - **`timeout_seconds`** — **accepted and ignored.** A row that outlives the turn
93
+ that made it has nothing to time out of. Still accepted, and still validated
94
+ as a positive integer, so existing callers keep working.
95
+
96
+ There is no `url`: the page is the one your browser is already on, so navigate
97
+ there *before* handing over. That absence is not an oversight — it is why this
98
+ row needs you back and the other one does not.
99
+
100
+ ## Returns
101
+
102
+ ```python
103
+ {"status": "queued", "id": "inbox-1757280000000-a1b2c3"}
104
+ ```
105
+
106
+ A **receipt, not an answer**. `id` is the row it left behind.
107
+
108
+ The answer arrives later, as a prompt:
109
+
110
+ - **done** — they finished. Look at the page before assuming what state it is
111
+ in; they may have landed somewhere you did not expect.
112
+ - **declined** — they aborted. An answer to route around, not an error.
113
+ - **asked** — they want something from you before they can finish. Answer it,
114
+ then ask again.
115
+
116
+ ## Getting it wrong
117
+
118
+ **Branching on the return value.** `{"status": "queued"}` means the row was
119
+ filed. There is no `outcome` key, and `r["outcome"]` raises `KeyError`.
120
+
121
+ **Treating `done` as "the thing I wanted happened".** It means they pressed
122
+ Done. Check the page.
123
+
124
+ **Treating `declined` as an error.** It is an answer. Route around it and say
125
+ what you are doing instead.
126
+
127
+ **Handing over a page you have not navigated to.** There is no `url`. Whatever
128
+ your session's browser is on is what they get — and it is what you will be
129
+ brought back to when they open the row.
130
+
131
+ **Drawing a step blind when you are woken.** The app may be gone by then. If you
132
+ cannot attach, say so plainly rather than instructing them about a screen you
133
+ cannot see; the inbox draws "getting ready…" until you have actually put
134
+ something up, and silence is what that state is for.
135
+
136
+ **Using it for a question.** If you only need words, that is `request_answer`,
137
+ or `ask_user` when it has to be answered now. This costs the user a page to work
138
+ through.
139
+
140
+ **Typing the password yourself.** The reason this tool exists.
141
+
142
+ **Raising the exec's timeout to cover it.** There is nothing to cover — the call
143
+ returns at once. This used to be the mistake that wasted the most of the user's
144
+ time on this page, and it no longer exists.
145
+
146
+ ## See also
147
+
148
+ `request_user_action` — the same handoff for a page the user can reach alone,
149
+ which wakes nobody.
150
+
151
+ `request_answer`, `ask_user`, `drive`, `use_widget`, `browser_tabs`,
152
+ `permissions`
@@ -0,0 +1,122 @@
1
+ <!-- also: review, ready, done, hand back, finished, call for review -->
2
+ <!-- group: Talking to the user -->
3
+
4
+ # api.request_review(claim, demo=None, covers=None, missing=None) -> dict
5
+
6
+ Say this is ready to be looked at, and carry on. **Does not block. Not gated.**
7
+
8
+ ## Why this exists
9
+
10
+ Every way of reaching the user used to hold your turn open until they answered.
11
+ `walkthrough` blocked. `request_user_action` blocked. So there was no way to say *"I have finished and built you a demo"*
12
+ that was not also the way to say *"I am stuck and cannot proceed"* — and the
13
+ second one is louder. An agent that finished cleanly was **less** visible than
14
+ one that got stuck, because the stuck one held a row in the queue and the
15
+ finished one dropped into a list of git diffs at the bottom of the board.
16
+
17
+ This was the first call to do it the other way, and the argument it won has
18
+ since been applied to the handoffs themselves: `request_user_action`,
19
+ `request_operator_action` and `request_answer` all file
20
+ a row and return now. `walkthrough` and `ask_user` are what still block, and
21
+ they block because somebody is watching.
22
+
23
+ It leaves a row behind, bound to your branch, and returns immediately. Nothing
24
+ expires and nothing is waiting on you. End your turn straight afterwards; end
25
+ the session. The request waits.
26
+
27
+ **This is how a parallel agent hands work back.** Eight sessions finishing
28
+ overnight cannot each block a turn on a human who is asleep.
29
+
30
+ ## The shape
31
+
32
+ ```python
33
+ await api.attach_app(surface="panel", env="fresh", reach="install-and-sign-in")
34
+ # ... build it, drive it, save a walkthrough of it ...
35
+
36
+ await api.request_review(
37
+ claim="The provider picker opens from the row and remembers your choice.",
38
+ demo="provider-setup",
39
+ covers=["opens-from-row", "survives-restart"],
40
+ missing=["useful message when no CLI is installed"],
41
+ )
42
+ return "Done — raised for review."
43
+ ```
44
+
45
+ ## Options
46
+
47
+ - **`claim`** — one sentence on what is now true, **in the user's terms**. This
48
+ is the headline of a row read at a glance beside seven others, so it has to
49
+ survive being skimmed by somebody who has not read your transcript and is not
50
+ going to. "Drafts now save automatically", not "added a debounced `useEffect`
51
+ to `NoteEditor`". Under 200 characters. If the sentence names a file or a
52
+ function, rewrite it.
53
+
54
+ - **`demo`** — the **name of a saved notebook** (`api.notebook(title, cells,
55
+ save=True)`, kept under `.widgex/notebooks/`), never the demo text itself.
56
+ Walkthrough scripts are retired. It becomes the row's **Watch** button, the
57
+ shortest path from "an agent claims X" to a human seeing X happen. No file to
58
+ name? Leave `demo` out.
59
+
60
+ A name that does not exist is refused **now**, with the list of ones that do.
61
+ That is deliberate — this row outlives you, so a typo discovered at reading
62
+ time is discovered by somebody with no agent left to ask.
63
+
64
+ - **`covers`** — the obligations you are claiming to have met.
65
+
66
+ - **`missing`** — the obligations you did **not** meet. **Say them.** They are
67
+ shown plainly, with no alarm styling, and they sort your row *up* rather than
68
+ down. An agent that did three of four things and reports the fourth is worth
69
+ more than one that reports four, and this field is designed to cost you
70
+ nothing for saying so: a queue that punished honesty would stop receiving it.
71
+
72
+ ## Returns
73
+
74
+ ```python
75
+ {"raised": True, "id": "9f2c1a0b3e44", "branch": "feat/provider-picker",
76
+ "demo": "provider-setup", "covers": [...], "missing": [...]}
77
+ ```
78
+
79
+ `id` is stable for one claim on one branch. Raising the same claim again after a
80
+ fix **updates** that row rather than adding a second one saying almost the same
81
+ thing — eight agents each leaving three near-duplicates is how a queue stops
82
+ being read.
83
+
84
+ ## Choosing between this and asking for a verdict
85
+
86
+ | | |
87
+ |---|---|
88
+ | `request_review` | "Here is what I built. Look when you like." One row, no single claim to judge. |
89
+ | `phase_complete` | "I have finished the phase." A review card in your chat — Approve or Needs changes — and the answer prompts you back. Task leads, inside a phase. |
90
+ | `walkthrough` | "Let me show you, step by step, while you use it." **Blocks** for as long as a person takes. |
91
+
92
+ Neither is replaced and neither is discouraged. Both of the first two return at
93
+ once now, so the choice between them is no longer about your turn — it is about
94
+ whether you lead a task with a phase to close. Use `walkthrough`
95
+ when you want to sit with somebody while they try it, which is the one of the
96
+ three that genuinely needs you present.
97
+
98
+ ## Getting it wrong
99
+
100
+ **Raising one with no demo and no claim worth reading.** A row saying "finished
101
+ the task" is a row that gets skipped, and the work behind it waits longer than
102
+ if you had said nothing. If there is genuinely nothing to look at — a refactor,
103
+ a type fix, a build script — say what you did in your reply and do not raise
104
+ one; a queue padded with rows nobody can act on is worse than a short queue.
105
+
106
+ **Asking twice.** Calling `phase_complete` and then this, for the same
107
+ change, puts two rows in front of somebody for one piece of work.
108
+
109
+ **Omitting `missing` because it looks bad.** It is the field a manager most
110
+ wants and the one you are least inclined to write. Nothing in the row punishes
111
+ it.
112
+
113
+ ## See also
114
+
115
+ - `api.instruct(...)` / `api.await_user(...)` — giving the demo itself.
116
+ There is no file to save: a tour is decided as it is given, so `demo=`
117
+ names a notebook or nothing.
118
+ - `api.man('attach_app')` — `reach=`, and getting the app to the state a demo starts from
119
+ - `api.phase_complete(...)` — when you lead a task and its phase is done. It does
120
+ not block either; the review prompts you back.
121
+ - `api.request_operator_action(...)` — when you need somebody's hand on the page
122
+ you are driving rather than an opinion on a branch.
@@ -0,0 +1,126 @@
1
+ # api.request_user_action(action, detail=None, url=None, timeout_seconds=600) -> dict
2
+ <!-- group: Talking to the user -->
3
+
4
+ Hand a GUI task to the human, to do when they get to it. **Does not block. Not
5
+ gated.**
6
+
7
+ For the things you cannot do yourself: signing in, clearing a CAPTCHA,
8
+ completing 2FA, approving something in a vendor's own dashboard. The request
9
+ appears as a row in the user's inbox carrying the `url`, and they go and do it
10
+ whenever they reach it.
11
+
12
+ ## It does not block
13
+
14
+ Your turn finishes. The row waits, and nothing expires. When they answer, you
15
+ are prompted with what they said and carry on from there.
16
+
17
+ So the call is the **end** of a turn, not a pause in the middle of one. Say what
18
+ you are waiting on and stop:
19
+
20
+ ```python
21
+ await api.request_user_action(
22
+ "Log in to Vercel",
23
+ detail="I need an authenticated session to create the deploy token. "
24
+ "If you decline I'll write the steps out for you to run by hand.",
25
+ url="https://vercel.com/login",
26
+ )
27
+ return "Asked for a Vercel login — everything that does not need one is done."
28
+ ```
29
+
30
+ Writing the rest of the turn as though the answer were in hand is the way this
31
+ goes wrong. There is no outcome in the return value and there will not be one
32
+ before your turn ends.
33
+
34
+ ## It wakes nobody, and `url` is what pays for that
35
+
36
+ Opening this row starts no turn. It does not have to: the row says where the
37
+ work is, so it is still worth answering days later, with this session long gone.
38
+
39
+ That is the whole difference from `request_operator_action`, which is about the
40
+ page **your** browser is on and therefore has no url to give — opening one of
41
+ those wakes you to bring the page back up and walk them through it. Neither
42
+ draws a card at send time any more, so this is the distinction between them,
43
+ and picking the wrong verb costs the user either a turn they did not need or a
44
+ row they cannot act on.
45
+
46
+ ## Options
47
+
48
+ - **`action`** — the label on a button. Imperative and **under 60 characters**:
49
+ `"Log In"`, `"Approve the deploy"`. Longer is rejected with a `ValueError`
50
+ telling you to move the explanation into `detail`.
51
+ - **`detail`** — one line on why it is needed and what happens next. Say what
52
+ each outcome will mean, because the user is choosing between them. This is
53
+ **the whole of what the next you will know**, so write it for somebody who was
54
+ not here — it will be read after a compaction, quite possibly by a session
55
+ that has no memory of asking.
56
+ - **`url`** — `http`/`https` only. Where the work happens. Defaults to the page
57
+ this session's browser is already on.
58
+ - **`timeout_seconds`** — **accepted and ignored.** A row that outlives the turn
59
+ that made it has nothing to time out of; it sits in the inbox until it is
60
+ answered or deleted. Still accepted, and still validated as a positive
61
+ integer, so existing callers keep working.
62
+
63
+ ## Returns
64
+
65
+ ```python
66
+ {"status": "queued", "id": "inbox-1757280000000-a1b2c3"}
67
+ ```
68
+
69
+ A **receipt, not an answer**. `id` is the row it left behind. Nothing in this
70
+ dict says what the user did, because at the moment it returns they have not seen
71
+ it.
72
+
73
+ Declining is an ordinary answer, not an error. It arrives as a prompt like any
74
+ other, which is why `detail` has to say what you will do with each outcome.
75
+
76
+ ## Getting it wrong
77
+
78
+ **Branching on the return value.** `{"status": "queued"}` means the request was
79
+ filed, not done. There is no `outcome` key to read.
80
+
81
+ ```python
82
+ # Wrong — `outcome` is not in this dict, and would not be true if it were.
83
+ r = await api.request_user_action("Log In")
84
+ if r["outcome"] == "done":
85
+ ...
86
+
87
+ # Right — file it and end the turn. You are prompted when they answer.
88
+ await api.request_user_action("Log In", detail="I'll read the API key names once you're in.")
89
+ return "Waiting on the Vercel login."
90
+ ```
91
+
92
+ **Putting the explanation in `action`.** Anything over 60 characters is
93
+ rejected outright. The button says `"Log In"`; `detail` says why.
94
+
95
+ **Writing `detail` for yourself.** You will not read it. The next session will,
96
+ with none of your context, possibly next week.
97
+
98
+ **Using it for a decision.** If you want the user to *choose*, that is
99
+ `request_answer` — or `ask_user` when you need the answer before the next line
100
+ of code runs. This call is for work only they can perform.
101
+
102
+ **Using it while you are driving something.** This points at a page and trusts
103
+ the user to go there, which is right when the work is on a site you are not
104
+ attached to and wrong the moment you have a surface open — they get a link and
105
+ have to work out for themselves which of the things on their screen you meant.
106
+ Use `request_operator_action`, which brings you back to show them.
107
+
108
+ ```python
109
+ await api.attach_app(surface='chat', env='fresh')
110
+ # Not this: the user gets a link, and nobody to show them anything.
111
+ # await api.request_user_action("Sign in")
112
+ await api.request_operator_action("Sign in")
113
+ ```
114
+
115
+ **Raising the exec's timeout to cover it.** There is nothing to cover. The call
116
+ returns at once, so the tool call's own `timeout_seconds` is irrelevant here —
117
+ this used to be the commonest mistake on this page and it no longer exists.
118
+
119
+ ## See also
120
+
121
+ `request_operator_action` — the same handoff for the page you are driving, which
122
+ wakes you to guide them when they open it.
123
+
124
+ `request_answer` — a decision rather than a task, on the same non-blocking shape.
125
+
126
+ `ask_user`, `use_widget`, `drive`, `browser_screenshot`, `harness`
@@ -0,0 +1,71 @@
1
+ # api.return_card(card_id, finding) -> dict
2
+
3
+ <!-- also: reject_card, send_back, adjudicate, refuse_submission, bounce_card -->
4
+ <!-- group: Talking to the user -->
5
+
6
+ Send a submission back, saying what is missing. Refused in Plan mode.
7
+
8
+ ## Why a submission is adjudicated at all
9
+
10
+ An agent asking for review is making a claim about its own work. The
11
+ claimant is not the judge, so a claim reaches a human only once something else
12
+ has looked at it — and that something is you.
13
+
14
+ This is how you refuse one. The card goes back to **Doing**, your finding is
15
+ recorded on it, and the agent is woken with it.
16
+
17
+ ```python
18
+ await api.return_card(card.id, "The empty-list case has no test, and the claim says it does.")
19
+ ```
20
+
21
+ ## The same path a human's rejection takes
22
+
23
+ A human asking for changes on a review does exactly this: the note joins the
24
+ card's findings, the card returns to Doing, and the next turn reads it back.
25
+ Deliberately one mechanism. Two would teach an agent that one of them is the
26
+ softer one, and it would be right.
27
+
28
+ ## Say what is missing, not that something is
29
+
30
+ > "No test covers the empty case" is a finding.
31
+ > "Not done yet" is a delay.
32
+
33
+ The agent reads this on its next turn and acts on it. A finding it cannot act on
34
+ costs it a turn to discover that, and costs the task's budget the same as a real
35
+ one.
36
+
37
+ ## Findings accumulate, and that is the specification
38
+
39
+ Nothing is written in advance. There is no checklist, and a card carries only
40
+ the text a human wrote on it — so the standard for "done" is built out of what
41
+ adjudication has actually found wanting, in order.
42
+
43
+ That list does three things at once:
44
+
45
+ - the **agent** is told what is still outstanding, specifically,
46
+ - **you** can see what you have already demanded, which is what stops you
47
+ contradicting yourself on the next pass,
48
+ - a **human** reading the card sees why it is on its third attempt.
49
+
50
+ ## Not gated, unlike `session_nudge`
51
+
52
+ Both wake somebody else and spend their tokens, so the difference is worth
53
+ knowing. `session_nudge` can wake any descendant at any moment. This can only
54
+ wake a session that has already asked to be judged and is sitting in Verify
55
+ waiting for an answer — the consequence is bounded by the thing that invited it,
56
+ and the task's budget ceiling bounds it again.
57
+
58
+ ## Arguments
59
+
60
+ - **`card_id`** — the card, as `tasks()` and `add_card` report it.
61
+ - **`finding`** — what is missing. Required, and refused when empty: a card sent
62
+ back with nothing to act on is a turn spent for nothing.
63
+
64
+ ## Returns
65
+
66
+ `{ id, lane, returned }`
67
+
68
+ ## See also
69
+
70
+ `api.man('update_card')` for moving a card between the lanes a human may set,
71
+ and `api.man('add_card')` for putting one on the board.