@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,150 @@
|
|
|
1
|
+
# Phases — phase / open / add / abort / delegate / verify
|
|
2
|
+
<!-- also: phase_open, phase_add_step, phase_abort_step, delegate_steps, phase_complete, phases, steps -->
|
|
3
|
+
<!-- group: Sessions -->
|
|
4
|
+
|
|
5
|
+
A task's work is split into **phases**, worked in order: alpha, beta, gamma and
|
|
6
|
+
on. Only one is current. A phase is a contract:
|
|
7
|
+
|
|
8
|
+
- a **design document** — the feature spec the phase is verified against, kept
|
|
9
|
+
in the task's folder as `phases/<name>/plan.md`;
|
|
10
|
+
- the **files** that came with it (images, mockups), copied into
|
|
11
|
+
`phases/<name>/files/` — open them by path;
|
|
12
|
+
- a **lead**, the task's lead unless it says otherwise;
|
|
13
|
+
- a list of **steps**: one or two sentences each, numbered within the phase.
|
|
14
|
+
A step is never a spec. The document is.
|
|
15
|
+
|
|
16
|
+
The user watches all of it in the task's **Overview** tab, and the steps you
|
|
17
|
+
are on sit on a dial over your chat box.
|
|
18
|
+
|
|
19
|
+
## Your todos are your steps
|
|
20
|
+
|
|
21
|
+
While a phase is open, the todo list you keep — TaskCreate/TaskUpdate,
|
|
22
|
+
TodoWrite, or [`todo_write`](todo_write.md) — **is** the list of steps assigned
|
|
23
|
+
to you:
|
|
24
|
+
|
|
25
|
+
| Todo | Step |
|
|
26
|
+
|---|---|
|
|
27
|
+
| a new item | a new step, assigned to you |
|
|
28
|
+
| `in_progress` / `completed` | started / marked off |
|
|
29
|
+
| deleted, or dropped from a `todo_write` | aborted |
|
|
30
|
+
|
|
31
|
+
Steps can be marked off in any order — finish step 4 before step 3 if that is
|
|
32
|
+
how the work lands. Moving a step that is not yours, or one that was aborted,
|
|
33
|
+
is refused, and nothing in that call is applied. A step a person
|
|
34
|
+
added from the Overview tab joins your list as soon as you write it there.
|
|
35
|
+
|
|
36
|
+
## How a phase opens
|
|
37
|
+
|
|
38
|
+
If you lead the task, **an approved plan opens the phase**: the plan you wrote
|
|
39
|
+
in plan mode becomes the document. Write your steps as todos straight after.
|
|
40
|
+
Re-planning an open phase replaces its document and keeps its steps.
|
|
41
|
+
|
|
42
|
+
Outside plan mode:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
await api.phase_open(
|
|
46
|
+
"Drafts autosave",
|
|
47
|
+
doc=open("docs/autosave.md").read(),
|
|
48
|
+
files=["design/autosave-states.png"], # copied into the task's folder
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Reading it
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
p = await api.phase()
|
|
56
|
+
p["phase"]["plan_path"] # read this — it is the spec
|
|
57
|
+
p["phase"]["files"] # absolute paths to the copies
|
|
58
|
+
p["phase"]["your_steps"] # [1, 2, 5]
|
|
59
|
+
p["phase"]["your_next_step"] # 2
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`phase` is `None` when the current phase has not been opened yet.
|
|
63
|
+
|
|
64
|
+
## Steps without a todo list
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
await api.phase_add_step("Show a saved indicator under the title")
|
|
68
|
+
await api.phase_abort_step(6, reason="covered by step 4")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Only a step's assignee or the phase lead can abort it.
|
|
72
|
+
|
|
73
|
+
## Delegating (lead only)
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
await api.delegate_steps([3, 4]) # a new agent in this task
|
|
77
|
+
await api.delegate_steps([5], to="drafts/session2") # one already here
|
|
78
|
+
await api.delegate_steps([5], to="drafts/session1") # naming yourself takes it back
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The delegate is sent the phase document, the paths of its files, and its steps,
|
|
82
|
+
and works them as its own todos. On your side it is a **background task**: keep
|
|
83
|
+
working, and you are woken when its last step is done — or if it fails or is
|
|
84
|
+
closed with steps still open, in which case they stay assigned to it for you to
|
|
85
|
+
hand on.
|
|
86
|
+
|
|
87
|
+
## Finishing a phase (lead only)
|
|
88
|
+
|
|
89
|
+
Two calls, for two different questions. Both need every step done or aborted.
|
|
90
|
+
|
|
91
|
+
### "I have finished it" — `phase_complete`
|
|
92
|
+
|
|
93
|
+
When you are done implementing and want a person to review the phase:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
await api.phase_complete(
|
|
97
|
+
"Drafts now save on their own and come back after a reload.",
|
|
98
|
+
checks=["Type in a new draft, wait a second, reload: the text is still there"],
|
|
99
|
+
)
|
|
100
|
+
return "Phase beta is up for review." # end the turn here
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
First it puts a **review gate** in your chat: should an adversarial review run
|
|
104
|
+
before the phase is approved? **Allow** or **Skip**, and it is the person's
|
|
105
|
+
call, not yours — do not run a review of your own before calling this.
|
|
106
|
+
|
|
107
|
+
- **Skip** puts the approval card up at once and does not wake you.
|
|
108
|
+
- **Allow**, or words typed instead (what the review should look at), opens
|
|
109
|
+
the phase again and wakes you with a brief: spawn 2–3 read-only reviewers
|
|
110
|
+
told to find where the phase's claims do not hold, add what must be fixed
|
|
111
|
+
as steps, fix it, and call `phase_complete` again. That second call goes
|
|
112
|
+
straight to the approval card; say in its summary what the review found.
|
|
113
|
+
|
|
114
|
+
The approval card is a **question card in your own chat** — the phase, your
|
|
115
|
+
summary, the checks, and two answers: **Approve** or **Needs changes**. After
|
|
116
|
+
Needs changes, the next `phase_complete` asks the gate again. Nothing to attach
|
|
117
|
+
first: the person reading your conversation is who reviews it. They can also
|
|
118
|
+
type instead of choosing, which counts as changes, with what they typed as the
|
|
119
|
+
notes.
|
|
120
|
+
|
|
121
|
+
It **does not wait**, and the card does not expire. It is stored like an inbox
|
|
122
|
+
row: it stays in your chat until somebody answers, comes back after a reload,
|
|
123
|
+
and can be answered from the inbox too. So end your turn straight afterwards —
|
|
124
|
+
it returns `{"status": "queued", "id", ...}` — and the answer wakes you with
|
|
125
|
+
what was decided:
|
|
126
|
+
|
|
127
|
+
- **Approve** closes the phase and moves the task to the next letter.
|
|
128
|
+
- **Needs changes**, or words typed instead, opens it again with what they
|
|
129
|
+
wrote. Add steps for what needs doing and call it once more when they are
|
|
130
|
+
done.
|
|
131
|
+
|
|
132
|
+
More work while a card is up — a step added, or a todo taken up — opens the
|
|
133
|
+
phase again by itself and withdraws the gate or approval card: neither
|
|
134
|
+
describes the phase any more. Call `phase_complete` again when that work is
|
|
135
|
+
done, and the person is asked about all of it.
|
|
136
|
+
|
|
137
|
+
When the phase changed something visible, attach the app and drive it to where
|
|
138
|
+
the checks can be tried *before* calling this, so the reviewer has it in front
|
|
139
|
+
of them. The review card is the one way a phase is closed: there is no separate
|
|
140
|
+
in-app "does this work?" card.
|
|
141
|
+
|
|
142
|
+
## Returns
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
api.phase() -> {"task", "you", "current", "phase": {...} | None, "done": [...]}
|
|
146
|
+
api.phase_open(...) -> the phase, as api.phase() describes it
|
|
147
|
+
api.phase_add_step -> {"n", "phase", "assignee", "reopened"?: True, "note"?}
|
|
148
|
+
api.delegate_steps -> {"delegation", "steps", "to", "spawned", "background": True}
|
|
149
|
+
api.phase_complete -> {"status": "queued", "id", "phase", "note", "gate"?: True}
|
|
150
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# api.powershell(script, timeout=120, background=False) -> dict
|
|
2
|
+
<!-- group: Coding -->
|
|
3
|
+
<!-- also: pwsh, ps1, Get-CimInstance, Start-Sleep, Stop-Process, windows -->
|
|
4
|
+
|
|
5
|
+
Run a PowerShell script exactly as written. **Gated** (it asks as `bash`).
|
|
6
|
+
**Never raises — read `exit_code`.**
|
|
7
|
+
|
|
8
|
+
## Why this exists
|
|
9
|
+
|
|
10
|
+
Through `api.bash`, PowerShell sat inside bash double quotes, and bash spoke
|
|
11
|
+
first. `$_` is bash's own variable — the last argument of the previous command —
|
|
12
|
+
so `ForEach-Object { $_.Name }` reached PowerShell as
|
|
13
|
+
`ForEach-Object { /usr/bin/bash.Name }`. `$p`, `$s` and `$PSItem` became
|
|
14
|
+
nothing at all. And `powershell -Command` exits **0** after most errors, so a
|
|
15
|
+
`Stop-Process` that stopped nothing came back looking like success, and the
|
|
16
|
+
next step ran against the process it had meant to kill.
|
|
17
|
+
|
|
18
|
+
`powershell` never puts the script on a command line, so nothing interprets it
|
|
19
|
+
but PowerShell; and any error ends the script with exit code 1 and the error on
|
|
20
|
+
stderr.
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
r = await api.powershell(r'''
|
|
26
|
+
Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
|
|
27
|
+
Where-Object { $_.CommandLine -like '*vite*' } |
|
|
28
|
+
ForEach-Object { Stop-Process -Id $_.ProcessId -Force; $_.ProcessId }
|
|
29
|
+
''')
|
|
30
|
+
if r["exit_code"] != 0:
|
|
31
|
+
return r["stderr"]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use a raw string, `r'''…'''`: a Windows path or a regex in a normal string has
|
|
35
|
+
its backslashes consumed by your own Python before PowerShell gets it.
|
|
36
|
+
|
|
37
|
+
## What it sets up
|
|
38
|
+
|
|
39
|
+
Before your script, on one line:
|
|
40
|
+
|
|
41
|
+
- every cmdlet defaulting to `-ErrorAction Stop`, and a `trap` that writes the
|
|
42
|
+
error to stderr and exits 1. A failing cmdlet stops the script rather than
|
|
43
|
+
printing and carrying on. Native programs are not affected: `git fetch 2>&1`
|
|
44
|
+
printing progress to stderr does not stop anything.
|
|
45
|
+
- UTF-8 output, so non-ASCII text is not mangled by the console code page.
|
|
46
|
+
|
|
47
|
+
Because that is one line, a position PowerShell reports — `ps1:4 char:9` — is
|
|
48
|
+
your script's line plus one.
|
|
49
|
+
|
|
50
|
+
If the script's **last** statement is a program that fails (`git`, `cmd /c`),
|
|
51
|
+
`exit_code` is that program's code. A program failing earlier does not raise in
|
|
52
|
+
PowerShell and does not stop the script; check `$LASTEXITCODE` after it, or
|
|
53
|
+
call it with `api.run` instead.
|
|
54
|
+
|
|
55
|
+
The script is written to a temporary `.ps1` exactly as given, line endings
|
|
56
|
+
included, and deleted as soon as the process ends — background runs too.
|
|
57
|
+
|
|
58
|
+
## Returns
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
{"stdout": str, "stderr": str, "exit_code": int, "program": str}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Windows PowerShell 5.1 on Windows; `pwsh` elsewhere, if installed. A missing
|
|
65
|
+
PowerShell is `exit_code` 127.
|
|
66
|
+
|
|
67
|
+
## Waiting
|
|
68
|
+
|
|
69
|
+
To wait, do not shell out to `Start-Sleep`: `await asyncio.sleep(seconds)` does
|
|
70
|
+
it in place.
|
|
71
|
+
|
|
72
|
+
## See also
|
|
73
|
+
|
|
74
|
+
`run`, `bash`, `sandbox`
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# api.profiles() -> dict
|
|
2
|
+
<!-- group: Working on a website -->
|
|
3
|
+
|
|
4
|
+
The browser identities on this machine, and which one you are using.
|
|
5
|
+
|
|
6
|
+
Ungated: listing opens nothing, starts nothing and acts as nobody.
|
|
7
|
+
`api.use_profile` is the one that has consequences.
|
|
8
|
+
|
|
9
|
+
## Returns
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
{
|
|
13
|
+
"profiles": [{"id": str, "name": str, "active": bool, "running": bool}],
|
|
14
|
+
"current": str | None,
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **`active`** — the profile the *picker* is set to. A per-window setting a
|
|
19
|
+
human chose.
|
|
20
|
+
- **`current`** — the profile *this session* is browsing as, and therefore what
|
|
21
|
+
your next `drive` will use.
|
|
22
|
+
- **`running`** — whether that profile's browser is up. `False` is ordinary;
|
|
23
|
+
`use_profile` starts it.
|
|
24
|
+
|
|
25
|
+
**`active` and `current` are allowed to differ, and often should.** Two sessions
|
|
26
|
+
can hold two identities at once, so there is no single answer to "which profile
|
|
27
|
+
is WidgeX using" — only "which one is *this* session using", which is `current`.
|
|
28
|
+
If you want to know who you are about to act as, read `current`, never `active`.
|
|
29
|
+
|
|
30
|
+
## Examples
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
state = await api.profiles()
|
|
34
|
+
if state["current"] is None:
|
|
35
|
+
await api.use_profile("personal")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Picking the one that is already warm, when either would do:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
state = await api.profiles()
|
|
42
|
+
warm = next((p for p in state["profiles"] if p["running"]), None)
|
|
43
|
+
if warm:
|
|
44
|
+
await api.use_profile(warm["name"])
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Getting it wrong
|
|
48
|
+
|
|
49
|
+
**Reading `active` to decide what you are driving.** That is the human's picker
|
|
50
|
+
setting, not your session's identity. `current` is yours.
|
|
51
|
+
|
|
52
|
+
**Treating an empty list as broken.** A machine with no profiles yet is a normal
|
|
53
|
+
first run. One is created from the browser widget.
|
|
54
|
+
|
|
55
|
+
**Calling it in a loop to wait for `running`.** `use_profile` does not return
|
|
56
|
+
until the browser is up, so there is nothing to poll for.
|
|
57
|
+
|
|
58
|
+
## See also
|
|
59
|
+
|
|
60
|
+
`use_profile`, `drive`, `request_operator_action`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# api.project_drive / project_drive_suggest — the project's WidgeX Drive
|
|
2
|
+
<!-- also: project_drive_suggest, widgex_drive, team_drive, documents, team_documents -->
|
|
3
|
+
<!-- group: Coding -->
|
|
4
|
+
|
|
5
|
+
Every project has a **Drive**: the team's documents, kept like code in git.
|
|
6
|
+
Every approved version of every document is in it, and teammates get it
|
|
7
|
+
through the project's own sync. A Google Drive or OneDrive folder may be linked
|
|
8
|
+
as a remote, but the Drive works without one.
|
|
9
|
+
|
|
10
|
+
Not the same thing as a drive space (`man('drive_space')`), which opens a
|
|
11
|
+
drive folder *as the workspace*. The project Drive sits beside any workspace.
|
|
12
|
+
|
|
13
|
+
## Where the documents are
|
|
14
|
+
|
|
15
|
+
`api.project_drive()` returns `folder`: a real folder on this machine holding
|
|
16
|
+
a copy of every document. It is the same copy the person edits — they open
|
|
17
|
+
Word, PowerPoint and Excel files in Google's editor inside WidgeX, and what
|
|
18
|
+
they write there comes back into these files. Read and edit the files there
|
|
19
|
+
with your ordinary file tools; there is no special read or write call.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
d = await api.project_drive()
|
|
23
|
+
d['folder'] # '/Users/…/.widgex/project-drives/widgex-1a2b3c4d/files'
|
|
24
|
+
d['files'] # what is in the Drive
|
|
25
|
+
d['changed'] # what differs in the copy: added, modified or deleted
|
|
26
|
+
d['changes'] # changes open or sent back, with their comments
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`drive_moved_on` on a changed file means a teammate's change to it landed
|
|
30
|
+
after the copy was taken. Suggesting yours would replace theirs; say so in the
|
|
31
|
+
title, or ask the person first.
|
|
32
|
+
|
|
33
|
+
## Getting an edit into the Drive
|
|
34
|
+
|
|
35
|
+
Nothing you write reaches the Drive by itself. When the documents are ready:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
await api.project_drive_suggest(['Pitch deck.pptx'], 'Tighten slides 3–5')
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That makes a **change**. It appears on the Drive page, where a person looks
|
|
42
|
+
at each document before and after, then approves it or sends it back with a
|
|
43
|
+
note. Approved, it lands for everyone and, if a remote is linked, is pushed
|
|
44
|
+
there in place. Sent back, the note is in the change's `comments`; edit the
|
|
45
|
+
files again, and the person presses Suggest again.
|
|
46
|
+
|
|
47
|
+
Only files listed under `changed` can be suggested. A `.pptx`, `.docx` or
|
|
48
|
+
`.xlsx` you edit should stay in that format — a Google document in a linked
|
|
49
|
+
folder is written back from it.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# api.read_bulletin() -> str
|
|
2
|
+
<!-- group: Talking to other agents -->
|
|
3
|
+
|
|
4
|
+
Read what every other agent in this codebase is working on. Not gated.
|
|
5
|
+
|
|
6
|
+
The bulletin is a board this machine keeps for this repository. Every agent
|
|
7
|
+
that has published a task list is on it, and each agent's list is exactly what
|
|
8
|
+
it last sent to `todo_write` — or to the native `TodoWrite` tool, which posts to
|
|
9
|
+
the same place.
|
|
10
|
+
|
|
11
|
+
It is a **hint, not a lock**. Nothing on it reserves a file or blocks a write.
|
|
12
|
+
It exists so you can notice a collision before it happens rather than after.
|
|
13
|
+
|
|
14
|
+
## Returns
|
|
15
|
+
|
|
16
|
+
A string. One block per agent:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
LOCAL BULLETIN — 2 other agents have an active task list in this codebase.
|
|
20
|
+
Positions are 1-based and are what api.wait_for_task(node, n) refers to.
|
|
21
|
+
|
|
22
|
+
AGENT N1 ACTIVE WORK:
|
|
23
|
+
1. [x] Read XylonMountConfig for the file-watch shape
|
|
24
|
+
2. [~] Wire the bulletin store into setSessionTodos
|
|
25
|
+
3. [ ] Add api.read_bulletin and api.wait_for_task
|
|
26
|
+
|
|
27
|
+
AGENT N2/E1 ACTIVE WORK: (fork, running in a clone)
|
|
28
|
+
1. [~] Bisect the crash
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`[x]` is completed, `[~]` is in progress, `[ ]` is pending. The number is the
|
|
32
|
+
`n` for `wait_for_task`.
|
|
33
|
+
|
|
34
|
+
An empty board comes back as a **single sentence** saying so, never as an empty
|
|
35
|
+
string — a call that returns nothing reads as a broken call.
|
|
36
|
+
|
|
37
|
+
The header line for your own entry is annotated `(this is you)`. Other
|
|
38
|
+
annotations you may see: `(harness)`, `(fork, running in a clone)`, and
|
|
39
|
+
`(last seen 4m ago)` for an agent that has not touched its list in a while.
|
|
40
|
+
|
|
41
|
+
## How agents get on and off it
|
|
42
|
+
|
|
43
|
+
You go **on** the board the first time you call `todo_write` with anything
|
|
44
|
+
unfinished. You come **off** it when every task on your list is `completed`, or
|
|
45
|
+
when your session ends.
|
|
46
|
+
|
|
47
|
+
That means an agent with nothing on the board is not necessarily idle — it may
|
|
48
|
+
simply never have published a list. Absence is not evidence.
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
Before starting something that sounds like it might already be underway:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
board = await api.read_bulletin()
|
|
56
|
+
if "provider catalog" in board:
|
|
57
|
+
return f"Someone is already on this:\n\n{board}"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
After noticing a file changed under you:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
return await api.read_bulletin()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Getting it wrong
|
|
67
|
+
|
|
68
|
+
**Treating it as a lock.** Nothing stops two agents editing one file, and
|
|
69
|
+
nobody is waiting on you. Reading the board tells you a collision is likely; it
|
|
70
|
+
does not prevent one, and there is no way to claim anything.
|
|
71
|
+
|
|
72
|
+
**Polling it in a loop.** If you actually need to wait for someone, that is
|
|
73
|
+
`wait_for_task`, which blocks properly. A loop around `read_bulletin` burns
|
|
74
|
+
turns and finds out no sooner.
|
|
75
|
+
|
|
76
|
+
**Assuming your own list is on it.** You appear once you have called
|
|
77
|
+
`todo_write`, not before. If you have not published a list, other agents cannot
|
|
78
|
+
see what you are doing either — which is the more important half.
|
|
79
|
+
|
|
80
|
+
**Reading an absent agent as an idle one.** A session that never published a
|
|
81
|
+
list never appears, however busy it is.
|
|
82
|
+
|
|
83
|
+
**Leaving finished work marked `in_progress`.** You only leave the board when
|
|
84
|
+
every task is complete. Until then everyone else is reading a list that says
|
|
85
|
+
you are still inside work you finished an hour ago.
|
|
86
|
+
|
|
87
|
+
## See also
|
|
88
|
+
|
|
89
|
+
`wait_for_task`, `todo_write`, `bulletin`, `relay`, `sessions`
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# api.read_file(path, offset=0, limit=None, raw=False) -> str
|
|
2
|
+
<!-- group: Coding -->
|
|
3
|
+
|
|
4
|
+
Read a file. Returns text with line numbers prepended — or, with `raw=True`,
|
|
5
|
+
exactly what is on disk. Not gated.
|
|
6
|
+
|
|
7
|
+
## Options
|
|
8
|
+
|
|
9
|
+
- **`path`** — absolute, or relative to the shell's cwd. `/c/Users/...`, `/tmp`
|
|
10
|
+
and `~` mean what they mean in `api.bash`.
|
|
11
|
+
- **`offset`** — first line to return, **0-based**. `offset=0` starts at the
|
|
12
|
+
top; `offset=100` skips the first 100 lines.
|
|
13
|
+
- **`limit`** — how many lines to return. `None` (default) means the rest of
|
|
14
|
+
the file.
|
|
15
|
+
- **`raw`** — return the text itself: no numbers, trailing whitespace kept,
|
|
16
|
+
`\r\n` kept. This is the form to parse (`json.loads`), to search with `re`,
|
|
17
|
+
or to change and hand back to `write_file`. It is what `cat` through
|
|
18
|
+
`api.bash` was being used for.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import json
|
|
22
|
+
pkg = json.loads(await api.read_file("package.json", raw=True))
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Decoded as UTF-8 with `errors="replace"`, so a binary or mis-encoded file comes
|
|
26
|
+
back full of `\ufffd` rather than raising.
|
|
27
|
+
|
|
28
|
+
## Returns
|
|
29
|
+
|
|
30
|
+
One string. Each line is formatted as:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
{line_number:>6}\t{line_content}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
— the number right-aligned in six columns, then a **tab**, then the line with
|
|
37
|
+
trailing whitespace stripped. Numbering is 1-based and accounts for `offset`,
|
|
38
|
+
so `offset=100` starts the display at 101.
|
|
39
|
+
|
|
40
|
+
An empty file, or an `offset` past the end, returns `""`.
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
return await api.read_file("src/index.ts")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A window in the middle of a large file:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
return await api.read_file("src/index.ts", offset=400, limit=80)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Getting the file's *actual* text, unnumbered, to build an `edit_file` anchor:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
raw = (await api.bash("sed -n '40,60p' src/index.ts"))["stdout"]
|
|
58
|
+
return raw
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Getting it wrong
|
|
62
|
+
|
|
63
|
+
**Feeding the output back into `edit_file`.** This is the most common mistake
|
|
64
|
+
with this call.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
chunk = await api.read_file("a.py", offset=10, limit=3)
|
|
68
|
+
# chunk is " 11\tdef go():\n 12\t return 1"
|
|
69
|
+
# Wrong — that string does not occur anywhere in a.py.
|
|
70
|
+
await api.edit_file("a.py", chunk, "...")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The numbers and tabs are display formatting. So is the stripped trailing
|
|
74
|
+
whitespace. Use `read_file` to *locate* an edit and to understand the code;
|
|
75
|
+
write `old_string` as the file actually contains it, or pull raw text with
|
|
76
|
+
`api.bash("cat ...")` / `sed -n`.
|
|
77
|
+
|
|
78
|
+
**Off-by-one on `offset`.** `offset` is 0-based but the displayed numbers are
|
|
79
|
+
1-based. To start at the line displayed as 250, pass `offset=249`.
|
|
80
|
+
|
|
81
|
+
**Reading a whole large file to find one thing.** `api.grep` gives you file,
|
|
82
|
+
line and text without spending the context. Grep first, then read the region
|
|
83
|
+
around the hit.
|
|
84
|
+
|
|
85
|
+
**Using it to test existence.** A missing file raises `FileNotFoundError`,
|
|
86
|
+
which kills the exec. `await api.glob(path)` returns `[]` instead.
|
|
87
|
+
|
|
88
|
+
## See also
|
|
89
|
+
|
|
90
|
+
`edit_file`, `write_file`, `grep`, `glob`, `bash`
|