@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,216 @@
|
|
|
1
|
+
# Relay — send / inbox / connect / close
|
|
2
|
+
<!-- also: relay_connect, relay_send, relay_inbox, relay_accept, relay_decline, relay_close -->
|
|
3
|
+
<!-- group: Talking to other agents -->
|
|
4
|
+
|
|
5
|
+
Message another session, on this machine or another one. None is gated.
|
|
6
|
+
|
|
7
|
+
## Two kinds of relay
|
|
8
|
+
|
|
9
|
+
**A message** — `relay_send(node, subject, body)` to any session. There is no
|
|
10
|
+
channel to open and nothing for the other side to accept: it is in their inbox
|
|
11
|
+
as soon as their machine has read it, they are told once, and they read it and
|
|
12
|
+
answer — or not — when they choose to.
|
|
13
|
+
|
|
14
|
+
**Delivered is not read, and does not start a turn.** If the recipient is busy it
|
|
15
|
+
sees the notice at its next tool result. If it is **idle**, a person at its
|
|
16
|
+
machine is asked whether to wake it (a session running Autonomous is woken
|
|
17
|
+
without asking, unless the message came from another account). Until someone
|
|
18
|
+
answers, or opens that chat, it stays idle with the message sitting in its
|
|
19
|
+
inbox — `relay_inbox()["sent"]` still says `delivered`, and for a session on
|
|
20
|
+
another machine a message can sit at `waiting` for the same reason. Neither
|
|
21
|
+
status says anyone is working on it. If you need an answer from an idle session
|
|
22
|
+
on someone else's machine, tell its person out of band to look for the wake
|
|
23
|
+
prompt.
|
|
24
|
+
|
|
25
|
+
**Forced** — orchestration, and nothing else. A parent may take a channel on a
|
|
26
|
+
**direct child** without asking it, one at a time, with
|
|
27
|
+
`relay_connect(node, force=True)`. The child does not consent and never reads an
|
|
28
|
+
inbox: your message arrives as its prompt, and your `relay_send` **blocks** until
|
|
29
|
+
its turn ends and returns what it said. You are its user, until you close it.
|
|
30
|
+
|
|
31
|
+
Reach for `force=True` when you spawned a session to do a job and you are the
|
|
32
|
+
one deciding what it does next. For anything else, send a message.
|
|
33
|
+
|
|
34
|
+
## How a message travels
|
|
35
|
+
|
|
36
|
+
It is a line in **your own transcript**. That is the whole of sending: the call
|
|
37
|
+
writes it and returns. Whichever machine is running the other session delivers
|
|
38
|
+
it — by appending it to *their* transcript — once it has read yours:
|
|
39
|
+
|
|
40
|
+
- **In this window** — delivered before `relay_send` returns: `status: 'delivered'`.
|
|
41
|
+
- **Another window here, or a machine the sync is linked to** — a few seconds.
|
|
42
|
+
- **A machine that is offline** — when it next syncs. Nothing has to be online
|
|
43
|
+
at the same time; the message waits in your transcript.
|
|
44
|
+
- **A session that is not running anywhere** — when it next runs.
|
|
45
|
+
|
|
46
|
+
You find out it arrived the same way: their transcript comes back with it in.
|
|
47
|
+
`relay_inbox()["sent"]` marks each of your messages `delivered` or `waiting`.
|
|
48
|
+
**Do not send it again** because it says `waiting` — it is already on its way,
|
|
49
|
+
and a second send is a second message.
|
|
50
|
+
|
|
51
|
+
A private task's transcript never leaves this machine, so a message from one
|
|
52
|
+
reaches only sessions here. The return says so when that is the case.
|
|
53
|
+
|
|
54
|
+
## Naming the other session
|
|
55
|
+
|
|
56
|
+
Every call takes a session the way notices and the bulletin print it:
|
|
57
|
+
`"fix-auth/session3"`. A session on another machine is named the same way, once
|
|
58
|
+
its transcript has synced here. `"."` is you and `".."` is your parent. A full
|
|
59
|
+
`repo/issue/session` and a bare session id also work. The older
|
|
60
|
+
`"@machine/issue/session"` form still resolves — the machine part is ignored,
|
|
61
|
+
because a session is the same session wherever it runs.
|
|
62
|
+
|
|
63
|
+
If the session id at the end of a name is one this machine holds, it resolves
|
|
64
|
+
even when the issue folder in front of it is spelled differently here (`-2`
|
|
65
|
+
suffixes appear when two folders collide). A task that has synced without any
|
|
66
|
+
session transcript in it has nothing to address — ask its owner for the session id.
|
|
67
|
+
|
|
68
|
+
A name that matches nothing, or more than one session, **raises** with who you
|
|
69
|
+
could have meant.
|
|
70
|
+
|
|
71
|
+
## `relay_send(node, subject, body, timeout=None) -> dict`
|
|
72
|
+
|
|
73
|
+
**The same call behaves differently for the two kinds.** This is the part that
|
|
74
|
+
gets missed.
|
|
75
|
+
|
|
76
|
+
A **message** returns at once:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
{"status": "delivered", "node": "fix-auth/session3", "id": "…"}
|
|
80
|
+
{"status": "sent", "node": "fix-auth/session3", "id": "…", "delivery": "…"}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`delivered` is in their inbox now. `sent` waits for their machine, and
|
|
84
|
+
`delivery` says how it will get there.
|
|
85
|
+
|
|
86
|
+
On a **forced** relay it blocks for the child's whole turn and returns its
|
|
87
|
+
answer:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
{"status": "done" | "timeout" | "error" | "gone" | "closed", "node": ..., "reply": str}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Check `status` before reading `reply`.** It is empty on `error`, where the
|
|
94
|
+
reason is in `error` instead — a crashed child otherwise reads exactly like one
|
|
95
|
+
that had nothing to say. `timeout` leaves the relay open and the child running,
|
|
96
|
+
so you can send again or close.
|
|
97
|
+
|
|
98
|
+
`subject` is one line, at most 120 characters: it rides in the tail of the other
|
|
99
|
+
agent's tool result, where a paragraph is noise. `timeout` applies to forced
|
|
100
|
+
sends only, defaults to 300 seconds — and, as with `session_join`, the exec's
|
|
101
|
+
own `timeout_seconds` must be higher or your code is killed mid-wait.
|
|
102
|
+
|
|
103
|
+
## `relay_inbox(msg=None) -> dict`
|
|
104
|
+
|
|
105
|
+
Everything by default:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
{"messages": [...], "count": int, "unread": int, "sent": [...], "forced": [...]}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Each message is `{msg, from, reply_to, subject, body, at, sent_at}`. `from` is
|
|
112
|
+
the name to answer with. `sent` is what you have sent, each with `status`
|
|
113
|
+
`delivered` or `waiting`. `forced` lists any forced relay you are part of and
|
|
114
|
+
your role in it.
|
|
115
|
+
|
|
116
|
+
With `msg=n`, just that one. **A number that does not exist returns
|
|
117
|
+
`{"message": None, "available": [...]}`** rather than raising — the number came
|
|
118
|
+
out of a notice you may have half-remembered, and killing the exec over a typo
|
|
119
|
+
would destroy the work around it.
|
|
120
|
+
|
|
121
|
+
Message numbers are a message's position in your transcript, so they are never
|
|
122
|
+
reused and never renumbered.
|
|
123
|
+
|
|
124
|
+
## `relay_connect(node, force=True, reason=None) -> dict`
|
|
125
|
+
|
|
126
|
+
Takes a forced relay on a direct child. Returns `{status: 'open' |
|
|
127
|
+
'already-open', node, connection_id, forced}`. Without `force` it returns
|
|
128
|
+
`{status: 'not-needed'}` — just send.
|
|
129
|
+
|
|
130
|
+
**Raises** for an unresolvable node, and for forcing anything that is not a
|
|
131
|
+
direct child running here, a child its state machine is driving, or a second
|
|
132
|
+
child while one is open.
|
|
133
|
+
|
|
134
|
+
## `relay_close(node) -> dict`
|
|
135
|
+
|
|
136
|
+
Ends a forced relay. Returns `{status: 'closed' | 'not-open', node}`. It does
|
|
137
|
+
**not** terminate anything — the child keeps running and keeps its work. Only the
|
|
138
|
+
parent may close one. A plain message has nothing to close.
|
|
139
|
+
|
|
140
|
+
## `relay_accept` / `relay_decline`
|
|
141
|
+
|
|
142
|
+
Kept so older scripts run; both return `{status: 'not-needed'}`. There is no
|
|
143
|
+
request to answer any more. To say no, reply saying so, or do not reply.
|
|
144
|
+
|
|
145
|
+
## How a message reaches you
|
|
146
|
+
|
|
147
|
+
You are told **once**, in the tail of a tool result, that mail arrived:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
──
|
|
151
|
+
Relay: 1 new message is in your inbox.
|
|
152
|
+
msg 7 from fix-auth/session3 — "schema for the auth tables"
|
|
153
|
+
Read them with `await api.relay_inbox()`, or one with `api.relay_inbox(msg=N)`.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
That notice is not repeated and nothing is waiting on you. Finish what you are
|
|
157
|
+
doing and read it at a natural break.
|
|
158
|
+
|
|
159
|
+
If you were idle, the person running you is asked whether to wake you for it —
|
|
160
|
+
or, in Autonomous mode, you are woken without asking, unless the message came
|
|
161
|
+
from somebody else's account.
|
|
162
|
+
|
|
163
|
+
## Examples
|
|
164
|
+
|
|
165
|
+
Messaging a peer:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
await api.relay_send("fix-auth/session3", "schema", "Which columns does users gain?")
|
|
169
|
+
# ... carry on working; the answer arrives in your inbox ...
|
|
170
|
+
return await api.relay_inbox()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Orchestrating a child:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
await api.relay_connect("fix-auth/session4", force=True)
|
|
177
|
+
r = await api.relay_send("fix-auth/session4", "scope", "Do the reads first, leave writes to me.",
|
|
178
|
+
timeout=600)
|
|
179
|
+
if r["status"] != "done":
|
|
180
|
+
return {"child": r["status"], "why": r.get("error")}
|
|
181
|
+
verdict = r["reply"]
|
|
182
|
+
await api.relay_close("fix-auth/session4")
|
|
183
|
+
return verdict
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Getting it wrong
|
|
187
|
+
|
|
188
|
+
**Reading `reply` without checking `status`.** The `session_join` failure,
|
|
189
|
+
exactly: a child that crashed comes back with an empty `reply` and looks like a
|
|
190
|
+
child that succeeded quietly.
|
|
191
|
+
|
|
192
|
+
**Sending again because it says `waiting`.** It is in your transcript and will
|
|
193
|
+
be delivered when their machine reads it. A second send is a second message.
|
|
194
|
+
|
|
195
|
+
**Leaving the exec timeout at its default on a forced send.** `timeout=600`
|
|
196
|
+
inside a tool call capped at 120 seconds means your code dies at 120 and the
|
|
197
|
+
child is left mid-turn.
|
|
198
|
+
|
|
199
|
+
**Forcing a grandchild, or a session you did not spawn.** It raises. Forcing
|
|
200
|
+
goes around whoever is actually orchestrating that session, which is the one
|
|
201
|
+
thing it must never do.
|
|
202
|
+
|
|
203
|
+
**Treating `relay_close` as terminate.** It ends the forced relay. The child
|
|
204
|
+
carries on. `session_terminate` is the other thing, and it is not this.
|
|
205
|
+
|
|
206
|
+
**Treating the tail notice as an interrupt.** It is announced once and then
|
|
207
|
+
waits. Abandoning a half-finished edit to read it is worse than reading it two
|
|
208
|
+
minutes later.
|
|
209
|
+
|
|
210
|
+
**Expecting a subagent to have an inbox.** `agent` runs a subagent, not a
|
|
211
|
+
session — it has no transcript of its own, so it cannot be an endpoint.
|
|
212
|
+
|
|
213
|
+
## See also
|
|
214
|
+
|
|
215
|
+
`session_join`, `agent`, `session_fork`, `sessions`, `read_bulletin`,
|
|
216
|
+
`bulletin`, `agent-vs-fork`
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!-- also: answer -->
|
|
2
|
+
<!-- group: Talking to the user -->
|
|
3
|
+
|
|
4
|
+
# api.reply(text) -> dict
|
|
5
|
+
|
|
6
|
+
Answer a question without taking the step away.
|
|
7
|
+
|
|
8
|
+
For what comes back as `asked`. The instruction, the ring and the step counter
|
|
9
|
+
all stay exactly as they were, and the answer appears on its own line
|
|
10
|
+
underneath — a word with somebody about the step they are on, rather than a new
|
|
11
|
+
step.
|
|
12
|
+
|
|
13
|
+
## Do not answer with `instruct`
|
|
14
|
+
|
|
15
|
+
`instruct` **is** a new step. It moves the ring, resets the counter and replaces
|
|
16
|
+
the words — so answering a question with it takes away the very sentence the
|
|
17
|
+
question was about, which is the thing somebody was asking you to explain.
|
|
18
|
+
|
|
19
|
+
That was the original behaviour and it was reported as exactly what it looked
|
|
20
|
+
like: asking cost you your place. A question is a side conversation about the
|
|
21
|
+
step, not a reason to leave it.
|
|
22
|
+
|
|
23
|
+
## Options
|
|
24
|
+
|
|
25
|
+
- **`text`** — the answer, in a sentence or two. It sits on a live card beside
|
|
26
|
+
the app, so it is skimmed rather than read; if the real answer is long, say
|
|
27
|
+
the short version here and the rest in the chat.
|
|
28
|
+
|
|
29
|
+
## Returns
|
|
30
|
+
|
|
31
|
+
`{"status": "answered"}`, or `{"error": ...}` when there is no card up to answer
|
|
32
|
+
on — which means the tour already ended, and the reply has nowhere to go.
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
The loop, with the answer folded in:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
while True:
|
|
40
|
+
r = await api.await_user(target)
|
|
41
|
+
if r.get("clicked") or r.get("advanced"):
|
|
42
|
+
break
|
|
43
|
+
if q := r.get("asked"):
|
|
44
|
+
await api.reply(answer_for(q))
|
|
45
|
+
continue # same step, still ringed, handed back to them
|
|
46
|
+
if r.get("stopped"):
|
|
47
|
+
break
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A question that deserves to move the tour instead:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
if q := r.get("asked"):
|
|
54
|
+
await api.reply("That one installs the CLI — let me show you.")
|
|
55
|
+
target = api.find(role="button", text_exact="Install")
|
|
56
|
+
await api.instruct("Here it is.", target, title="detour")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Getting it wrong
|
|
60
|
+
|
|
61
|
+
**Replying when nothing asked.** There is no card to write on once a tour has
|
|
62
|
+
ended, and the error says so rather than drawing one from nowhere.
|
|
63
|
+
|
|
64
|
+
**Answering in the chat and carrying on.** The tour is in its own window. An
|
|
65
|
+
answer typed into this conversation is in a window they are not looking at.
|
|
66
|
+
|
|
67
|
+
**Treating a question as an interruption.** It is the tour working. Answer it,
|
|
68
|
+
and go wherever it points — you are a guide, not a script.
|
|
69
|
+
|
|
70
|
+
## See also
|
|
71
|
+
|
|
72
|
+
`await_user`, `instruct`, `highlight`
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!-- also: reply, answer-back, inbox-reply -->
|
|
2
|
+
<!-- group: Talking to the user -->
|
|
3
|
+
|
|
4
|
+
# api.reply_message(id, text) -> dict
|
|
5
|
+
|
|
6
|
+
Answer back, in the inbox row you are being asked about.
|
|
7
|
+
|
|
8
|
+
When somebody answers one of your requests you are woken with what they said.
|
|
9
|
+
Often what they said is a question — and until this existed there was nowhere to
|
|
10
|
+
put the answer, so it went into the transcript.
|
|
11
|
+
|
|
12
|
+
**That is the same as not answering.** They asked in the row; they are watching
|
|
13
|
+
the row; your transcript is a different surface they are not looking at. This is
|
|
14
|
+
the failure that produced this call: an agent asked about a file, the reply was
|
|
15
|
+
*"what's the context of this?"*, and the explanation went somewhere the person
|
|
16
|
+
who asked never saw.
|
|
17
|
+
|
|
18
|
+
## The row comes back to them, to read
|
|
19
|
+
|
|
20
|
+
Replying puts the message back to unread — the dot comes back and the reply box
|
|
21
|
+
is theirs again — but it does **not** put it back to waiting on them. They
|
|
22
|
+
answered it once; that settled it. The task is not held in Blocked by your
|
|
23
|
+
reply, and it is not counted on the badge.
|
|
24
|
+
|
|
25
|
+
So if you still need something from them before you can go on — a verdict, a
|
|
26
|
+
decision, an action — file it as its own request after replying
|
|
27
|
+
(`request_verification`, `request_answer`, `request_operator_action`). That one
|
|
28
|
+
waits until it is answered, and it is drawn on the task's Card tab where they
|
|
29
|
+
can answer it.
|
|
30
|
+
|
|
31
|
+
That is also the reason not to reply with nothing. Every reply costs them a
|
|
32
|
+
second look at a row they had finished with.
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
- **`id`** — the message you are answering. It came back from the request you
|
|
37
|
+
sent, and the prompt that woke you names it. You may only reply to your own
|
|
38
|
+
messages; anything else is refused.
|
|
39
|
+
- **`text`** — what you want to say. Plain text; blank lines are paragraphs.
|
|
40
|
+
|
|
41
|
+
## Say it the way they asked it
|
|
42
|
+
|
|
43
|
+
The register rule from `api.man('request_answer')` applies here and matters
|
|
44
|
+
more, because they have already told you once that they could not follow you.
|
|
45
|
+
|
|
46
|
+
Answer the question they actually asked. If they asked what something is about,
|
|
47
|
+
that is a question about *their* work and their reasons — not about your call
|
|
48
|
+
stack. Do not explain the mechanism unless the mechanism is what they asked
|
|
49
|
+
about.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# they asked: "what's the context of this?"
|
|
53
|
+
|
|
54
|
+
# no
|
|
55
|
+
await api.reply_message(id, "It's the CSV parser refactor in legacy/csv.ts — "
|
|
56
|
+
"the shared implementation lacks the quoted-newline "
|
|
57
|
+
"handling that the local one has.")
|
|
58
|
+
|
|
59
|
+
# yes
|
|
60
|
+
await api.reply_message(id, "Sorry — it came out of the import work you asked "
|
|
61
|
+
"for last week. Two of the files read data in an "
|
|
62
|
+
"older way than the rest, and I wanted to know "
|
|
63
|
+
"whether to bring them in line now or leave them.")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## It does not block
|
|
67
|
+
|
|
68
|
+
Your turn finishes here. Their next answer wakes you again, the same way the
|
|
69
|
+
first one did.
|
|
70
|
+
|
|
71
|
+
## Returns
|
|
72
|
+
|
|
73
|
+
`{'status': 'queued', 'id': ...}` — a receipt, not an answer.
|
|
74
|
+
|
|
75
|
+
## See also
|
|
76
|
+
|
|
77
|
+
- `api.man('request_answer')` — asking in the first place, and the register rule
|
|
78
|
+
- `api.man('request_user_action')` — handing over work rather than a question
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# api.report_bug(attempted, expected, actual, severity, kind='defect', surface=None, blocked=False, workaround=None, reproduction=None, citations=None) -> dict
|
|
2
|
+
<!-- group: Reporting a WidgeX problem -->
|
|
3
|
+
|
|
4
|
+
File a bug against **WidgeX** — the environment you are running in. Not gated.
|
|
5
|
+
|
|
6
|
+
This has nothing to do with the project you are editing. You are almost
|
|
7
|
+
certainly working on someone's codebase; a limitation you hit *while* doing that
|
|
8
|
+
is exactly the report that is wanted.
|
|
9
|
+
|
|
10
|
+
File when an api call misbehaves, a capability does not exist, the sandbox
|
|
11
|
+
refuses something legitimate, a permission prompt hangs, a browser will not
|
|
12
|
+
attach, or a fork or harness fails.
|
|
13
|
+
|
|
14
|
+
## Options
|
|
15
|
+
|
|
16
|
+
- **`attempted`** — what you were trying to accomplish, in your own terms.
|
|
17
|
+
- **`expected`** — what you expected the environment to let you do.
|
|
18
|
+
- **`actual`** — what happened instead.
|
|
19
|
+
- **`severity`** — impact **on your ability to work**, not code severity:
|
|
20
|
+
`'critical'` (blocked, no workaround), `'major'`, `'minor'`, `'cosmetic'`.
|
|
21
|
+
- **`kind`** — `'defect'` when something misbehaved, `'missing_capability'`
|
|
22
|
+
when there was simply no way to do it, `'unclear'` when you cannot tell.
|
|
23
|
+
Default `'defect'`. **`'unclear'` is a real answer** and better than a
|
|
24
|
+
confident guess.
|
|
25
|
+
- **`surface`** — where you hit it: `files`, `shell`, `search`, `sessions`,
|
|
26
|
+
`harness`, `browser`, `xylon`, `permissions`, `ui`, `other`.
|
|
27
|
+
- **`blocked`** — `True` only if it stopped the work. If you got there another
|
|
28
|
+
way, this is `False` and the way is `workaround`.
|
|
29
|
+
- **`workaround`** — what you did instead.
|
|
30
|
+
- **`reproduction`** — minimal steps, if you know them.
|
|
31
|
+
- **`citations`** — WidgeX source lines, e.g. `["StdlibShell.ts:301"]`. Normally
|
|
32
|
+
omitted; you are not expected to know its internals.
|
|
33
|
+
|
|
34
|
+
**Write prose in every field.** Session state, environment, a transcript
|
|
35
|
+
excerpt, this turn's diff and recent logs are attached automatically. Pasting a
|
|
36
|
+
stack trace in `actual` displaces the sentence that would have been useful.
|
|
37
|
+
|
|
38
|
+
## Returns
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
{"report_id": str, "path": str, "similar": [...]}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`similar` lists earlier reports with the same signature. **Check it before
|
|
45
|
+
filing the same thing twice.**
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
A call that did not do what its signature says:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
return await api.report_bug(
|
|
53
|
+
attempted="load the github widget by name so I could drive the PR page",
|
|
54
|
+
expected="browser_load(name='github') to register that widget's states",
|
|
55
|
+
actual="it was accepted but the registry stayed empty, and browser_context "
|
|
56
|
+
"reported no widget loaded",
|
|
57
|
+
severity="major",
|
|
58
|
+
kind="defect",
|
|
59
|
+
surface="browser",
|
|
60
|
+
blocked=False,
|
|
61
|
+
workaround="passed the state definitions as code= instead",
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Something that simply does not exist:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
return await api.report_bug(
|
|
69
|
+
attempted="unbind the state machine from this harness while re-arming it",
|
|
70
|
+
expected="some way to clear the binding from the api",
|
|
71
|
+
actual="omitting machine= keeps the existing binding, and there is no value "
|
|
72
|
+
"that clears it — the only route is the Harness Manager UI",
|
|
73
|
+
severity="minor",
|
|
74
|
+
kind="missing_capability",
|
|
75
|
+
surface="harness",
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Not knowing what went wrong is fine:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
return await api.report_bug(
|
|
83
|
+
attempted="join a child session I had just extended",
|
|
84
|
+
expected="status done or timeout",
|
|
85
|
+
actual="status error with an empty reason; the child's node looks complete",
|
|
86
|
+
severity="major",
|
|
87
|
+
kind="unclear",
|
|
88
|
+
surface="sessions",
|
|
89
|
+
blocked=True,
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Getting it wrong
|
|
94
|
+
|
|
95
|
+
**Not filing because it is probably your fault.** Partial ownership is enough.
|
|
96
|
+
"I may have called it wrong" and "the environment is plausibly involved" are
|
|
97
|
+
both true at once, and only one of them is a reason to file.
|
|
98
|
+
|
|
99
|
+
**Not filing because you are not working on WidgeX.** You are never working on
|
|
100
|
+
WidgeX. That is the normal case.
|
|
101
|
+
|
|
102
|
+
**Treating "there is no way to do X" as a dead end.** It is a report —
|
|
103
|
+
`kind='missing_capability'` — and then you find another route. File and keep
|
|
104
|
+
going.
|
|
105
|
+
|
|
106
|
+
**Pasting logs, diffs or stack traces.** They are attached already, and the
|
|
107
|
+
fields cap out. Describe what happened.
|
|
108
|
+
|
|
109
|
+
**Filing once per retry.** One report per distinct limitation. Check `similar`.
|
|
110
|
+
|
|
111
|
+
**Telling the user you are blocked when you are not.** If you worked around it,
|
|
112
|
+
`blocked=False` and put the workaround in the report — not in an apology.
|
|
113
|
+
|
|
114
|
+
**Using it for a bug in the user's code.** That is not this. This is the IDE.
|
|
115
|
+
|
|
116
|
+
## See also
|
|
117
|
+
|
|
118
|
+
`sandbox`, `permissions`, `overview`
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<!-- also: ask, question, decide, decision, which one -->
|
|
2
|
+
<!-- group: Talking to the user -->
|
|
3
|
+
|
|
4
|
+
# api.request_answer(question, detail=None, choices=None) -> dict
|
|
5
|
+
|
|
6
|
+
Ask the user to decide something you cannot decide yourself. **Does not block.
|
|
7
|
+
Not gated.**
|
|
8
|
+
|
|
9
|
+
The other three handoffs give away *work* — a GUI task, a judgement.
|
|
10
|
+
`request_answer` gives away a **decision**: two defensible options and no way
|
|
11
|
+
for you to pick between them from inside the code.
|
|
12
|
+
|
|
13
|
+
## It does not block
|
|
14
|
+
|
|
15
|
+
Your turn finishes. The question waits as a row in the user's inbox, and
|
|
16
|
+
nothing expires. When they answer, you are prompted with what they said and
|
|
17
|
+
carry on from there.
|
|
18
|
+
|
|
19
|
+
So the call is the **end** of a turn, not a pause in the middle of one. Say what
|
|
20
|
+
you are waiting on and stop:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
await api.request_answer(
|
|
24
|
+
"Standardise how we read CSV files, or leave the odd one out?",
|
|
25
|
+
detail="Standardising means one way to maintain instead of two, and a day's work "
|
|
26
|
+
"now. Leaving it means the old path stays until something breaks it. "
|
|
27
|
+
"Either is fine by me — it is your call which we live with.",
|
|
28
|
+
choices=["standardise", "leave it"],
|
|
29
|
+
)
|
|
30
|
+
return "Asked about the CSV readers — everything else is done."
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Writing the rest of the turn as though the answer were in hand is the way this
|
|
34
|
+
goes wrong. There is no answer in the return value and there will not be one
|
|
35
|
+
before your turn ends.
|
|
36
|
+
|
|
37
|
+
## Who is reading it
|
|
38
|
+
|
|
39
|
+
Somebody who has not seen the code, and will not go and look.
|
|
40
|
+
|
|
41
|
+
This is the thing agents get wrong first, and it is not a style preference — it
|
|
42
|
+
decides whether the question is answerable at all. Your message lands in an
|
|
43
|
+
inbox beside the other things a person has to decide today. They will not open
|
|
44
|
+
the file. They do not know what the class is called. They will not ask you a
|
|
45
|
+
follow-up before deciding; they will answer wrongly, or leave it.
|
|
46
|
+
|
|
47
|
+
So: no file paths, no symbol names, no branch names, no test names. Name the
|
|
48
|
+
**trade-off**, in the words you would use saying it out loud.
|
|
49
|
+
|
|
50
|
+
A real message, and the reply it actually got:
|
|
51
|
+
|
|
52
|
+
> **Convert legacy/csv.ts to the shared parser?**
|
|
53
|
+
>
|
|
54
|
+
> It has hand-rolled quote handling that gets embedded newlines inside a quoted
|
|
55
|
+
> field right, and the shared parser does not. Convert it and we lose that case,
|
|
56
|
+
> which the fixtures do exercise. Leave it and we keep two parsers.
|
|
57
|
+
|
|
58
|
+
> — *what's the context of this?*
|
|
59
|
+
|
|
60
|
+
The same decision, asked so it can be answered:
|
|
61
|
+
|
|
62
|
+
> **Two ways to handle CSV imports — which matters more here?**
|
|
63
|
+
>
|
|
64
|
+
> One copes with unusual files that the other mangles. The other is half the
|
|
65
|
+
> code to maintain. I can go either way; I only need to know which you would
|
|
66
|
+
> rather live with.
|
|
67
|
+
|
|
68
|
+
Nothing was removed but the vocabulary. The second is shorter, and it is the
|
|
69
|
+
one a person can act on without opening anything.
|
|
70
|
+
|
|
71
|
+
**Three lines is plenty.** If the decision will not go in three, the thing to
|
|
72
|
+
send is a smaller decision.
|
|
73
|
+
|
|
74
|
+
## Options
|
|
75
|
+
|
|
76
|
+
- **`question`** — what you want decided, as a full sentence, in the user's
|
|
77
|
+
terms. It is the headline of a row read at a glance beside others, so it has
|
|
78
|
+
to make sense to somebody who has not read your transcript.
|
|
79
|
+
- **`detail`** — **what you will do with each answer.** The user is choosing
|
|
80
|
+
between outcomes, not between words. A question that does not say what
|
|
81
|
+
follows from it is one they cannot answer well, and they will ask you what
|
|
82
|
+
you meant instead of answering — a whole round trip for a sentence you could
|
|
83
|
+
have written.
|
|
84
|
+
- **`choices`** — labels that become buttons beside the reply box. A shortcut,
|
|
85
|
+
not a constraint: the reply box is always there and the user can write
|
|
86
|
+
something you did not offer. **Read what they wrote, do not match on the
|
|
87
|
+
label.**
|
|
88
|
+
|
|
89
|
+
## Returns
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
{"status": "queued", "id": "inbox-1757280000000-a1b2c3"}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A **receipt, not an answer**. `id` is the row it left behind. Nothing in this
|
|
96
|
+
dict tells you what the user decided, because at the moment it returns they have
|
|
97
|
+
not seen the question.
|
|
98
|
+
|
|
99
|
+
## Getting it wrong
|
|
100
|
+
|
|
101
|
+
**Comparing the answer against your own `choices` with `==`.** When the answer
|
|
102
|
+
arrives it is prose the user typed, and a label they clicked is only the most
|
|
103
|
+
likely case.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Wrong — silently takes the else branch when they typed anything at all.
|
|
107
|
+
if answer == "convert it":
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
# Right — read it.
|
|
111
|
+
if "convert" in answer.lower():
|
|
112
|
+
...
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Treating the return value as the decision.** `{"status": "queued"}` means the
|
|
116
|
+
question was filed, not answered. Branching on it branches on nothing.
|
|
117
|
+
|
|
118
|
+
**Asking what you could find out.** If the answer is in the repo, read the repo.
|
|
119
|
+
A question costs the user attention you can only spend once, and a row they did
|
|
120
|
+
not need is a row that makes the next one slower to reach.
|
|
121
|
+
|
|
122
|
+
**Asking for reassurance about a plan.** Permission prompts already cover the
|
|
123
|
+
gated calls. This is for a decision that changes *what* you build.
|
|
124
|
+
|
|
125
|
+
**Sending news.** An inbox row is something to act on. "Rebased onto main"
|
|
126
|
+
belongs in your reply; there is no request for it, and there is deliberately no
|
|
127
|
+
way to file one.
|
|
128
|
+
|
|
129
|
+
## See also
|
|
130
|
+
|
|
131
|
+
`ask_user` — the same question asked *now*, blocking your turn until they pick.
|
|
132
|
+
Reach for it when the answer changes what the very next line of code does and
|
|
133
|
+
you are sitting in a session somebody is watching. Reach for `request_answer`
|
|
134
|
+
when the answer changes what happens next *session*, or when nobody is watching
|
|
135
|
+
— eight agents finishing overnight cannot each hold a turn open on a human who
|
|
136
|
+
is asleep.
|
|
137
|
+
|
|
138
|
+
`request_review` — the same non-blocking shape for "this is ready to look at"
|
|
139
|
+
rather than "I need you to choose".
|
|
140
|
+
|
|
141
|
+
`request_user_action`, `request_operator_action`, `phase_complete`,
|
|
142
|
+
`man('overview')`
|