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