@widgex/wgx 0.0.0-stage → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE.txt +49 -0
  2. package/README.md +33 -2
  3. package/ThirdPartyNotices.txt +4685 -0
  4. package/package.json +30 -4
  5. package/provider-clis.json +1211 -0
  6. package/python/XACTOR.md +146 -0
  7. package/python/browser_actor_host.py +456 -0
  8. package/python/cdp_page.py +363 -0
  9. package/python/harness_check.py +77 -0
  10. package/python/requirements.txt +1 -0
  11. package/python/test_line_endings.py +185 -0
  12. package/python/xactor/__init__.py +18 -0
  13. package/python/xactor/__main__.py +226 -0
  14. package/python/xactor/api.py +882 -0
  15. package/python/xactor/bridge.py +784 -0
  16. package/python/xactor/dom.py +117 -0
  17. package/python/xactor/man/add_card.md +89 -0
  18. package/python/xactor/man/agent-vs-fork.md +108 -0
  19. package/python/xactor/man/agent.md +333 -0
  20. package/python/xactor/man/ask_model.md +110 -0
  21. package/python/xactor/man/ask_reference.md +56 -0
  22. package/python/xactor/man/ask_user.md +103 -0
  23. package/python/xactor/man/attach_app.md +595 -0
  24. package/python/xactor/man/attach_ide.md +65 -0
  25. package/python/xactor/man/attach_ide_surface.md +31 -0
  26. package/python/xactor/man/await_user.md +214 -0
  27. package/python/xactor/man/bash.md +142 -0
  28. package/python/xactor/man/browser-driver.md +137 -0
  29. package/python/xactor/man/browser_activity.md +53 -0
  30. package/python/xactor/man/browser_attach.md +84 -0
  31. package/python/xactor/man/browser_close_tab.md +50 -0
  32. package/python/xactor/man/browser_detach.md +50 -0
  33. package/python/xactor/man/browser_exec.md +66 -0
  34. package/python/xactor/man/browser_focus_tab.md +47 -0
  35. package/python/xactor/man/browser_hybrid.md +47 -0
  36. package/python/xactor/man/browser_open.md +50 -0
  37. package/python/xactor/man/browser_open_tab.md +58 -0
  38. package/python/xactor/man/browser_plumbing.md +107 -0
  39. package/python/xactor/man/browser_screenshot.md +56 -0
  40. package/python/xactor/man/browser_snapshot.md +52 -0
  41. package/python/xactor/man/browser_tabs.md +67 -0
  42. package/python/xactor/man/bulletin.md +88 -0
  43. package/python/xactor/man/capture_page.md +54 -0
  44. package/python/xactor/man/cd.md +64 -0
  45. package/python/xactor/man/chess_move.md +53 -0
  46. package/python/xactor/man/children.md +89 -0
  47. package/python/xactor/man/close_task.md +14 -0
  48. package/python/xactor/man/compact.md +75 -0
  49. package/python/xactor/man/drive.md +140 -0
  50. package/python/xactor/man/drive_space.md +72 -0
  51. package/python/xactor/man/driving.md +168 -0
  52. package/python/xactor/man/edit_file.md +112 -0
  53. package/python/xactor/man/finding.md +194 -0
  54. package/python/xactor/man/fork_agent.md +45 -0
  55. package/python/xactor/man/glob.md +83 -0
  56. package/python/xactor/man/grep.md +115 -0
  57. package/python/xactor/man/harness.md +165 -0
  58. package/python/xactor/man/harness_check.md +70 -0
  59. package/python/xactor/man/harness_format.md +257 -0
  60. package/python/xactor/man/highlight.md +66 -0
  61. package/python/xactor/man/instruct.md +111 -0
  62. package/python/xactor/man/ls.md +63 -0
  63. package/python/xactor/man/man.md +86 -0
  64. package/python/xactor/man/merge_wiz.md +40 -0
  65. package/python/xactor/man/notebook.md +98 -0
  66. package/python/xactor/man/overview.md +89 -0
  67. package/python/xactor/man/permissions.md +137 -0
  68. package/python/xactor/man/phase.md +150 -0
  69. package/python/xactor/man/powershell.md +74 -0
  70. package/python/xactor/man/profiles.md +60 -0
  71. package/python/xactor/man/project_drive.md +49 -0
  72. package/python/xactor/man/read_bulletin.md +89 -0
  73. package/python/xactor/man/read_file.md +90 -0
  74. package/python/xactor/man/relay.md +216 -0
  75. package/python/xactor/man/reply.md +72 -0
  76. package/python/xactor/man/reply_message.md +78 -0
  77. package/python/xactor/man/report_bug.md +118 -0
  78. package/python/xactor/man/request_answer.md +142 -0
  79. package/python/xactor/man/request_merge_decisions.md +121 -0
  80. package/python/xactor/man/request_operator_action.md +152 -0
  81. package/python/xactor/man/request_review.md +122 -0
  82. package/python/xactor/man/request_user_action.md +126 -0
  83. package/python/xactor/man/return_card.md +71 -0
  84. package/python/xactor/man/run.md +99 -0
  85. package/python/xactor/man/sandbox.md +144 -0
  86. package/python/xactor/man/session_fork.md +74 -0
  87. package/python/xactor/man/session_harness.md +109 -0
  88. package/python/xactor/man/session_join.md +102 -0
  89. package/python/xactor/man/session_land.md +73 -0
  90. package/python/xactor/man/session_nudge.md +73 -0
  91. package/python/xactor/man/session_rest.md +73 -0
  92. package/python/xactor/man/session_scope.md +84 -0
  93. package/python/xactor/man/session_terminate.md +58 -0
  94. package/python/xactor/man/session_token.md +78 -0
  95. package/python/xactor/man/sessions.md +93 -0
  96. package/python/xactor/man/set_harness.md +15 -0
  97. package/python/xactor/man/spawn_swarm.md +80 -0
  98. package/python/xactor/man/submit_evidence.md +54 -0
  99. package/python/xactor/man/swarm_brief.md +30 -0
  100. package/python/xactor/man/task_status.md +82 -0
  101. package/python/xactor/man/tasks.md +47 -0
  102. package/python/xactor/man/todo_write.md +104 -0
  103. package/python/xactor/man/update_card.md +70 -0
  104. package/python/xactor/man/use_profile.md +90 -0
  105. package/python/xactor/man/use_widget.md +82 -0
  106. package/python/xactor/man/wait_for_task.md +105 -0
  107. package/python/xactor/man/widget-model.md +284 -0
  108. package/python/xactor/man/widgets.md +59 -0
  109. package/python/xactor/man/write_file.md +76 -0
  110. package/python/xactor/man/writing-a-widget.md +142 -0
  111. package/python/xactor/man/xylon.md +120 -0
  112. package/python/xactor/man/xylon_blame.md +80 -0
  113. package/python/xactor/man/xylon_read.md +131 -0
  114. package/python/xactor/man/xylon_search.md +125 -0
  115. package/python/xactor/manual.py +195 -0
  116. package/python/xactor/parent_watch.py +115 -0
  117. package/python/xactor/plan_mode.py +350 -0
  118. package/python/xactor/sandbox.py +856 -0
  119. package/python/xactor/seals.py +438 -0
  120. package/python/xactor/shell_hints.py +210 -0
  121. package/python/xactor/states/__init__.py +13 -0
  122. package/python/xactor/states/base.py +523 -0
  123. package/python/xactor/states/registry.py +274 -0
  124. package/python/xactor/stdlib_api.py +6345 -0
  125. package/python/xactor/widgets/__init__.py +1 -0
  126. package/python/xactor/widgets/github_tokens.py +314 -0
  127. package/python/xactor/widgets/google_aistudio.py +365 -0
  128. package/settings-schema.json +403 -0
  129. package/viewer/index.css +160 -0
  130. package/viewer/index.js +15 -0
  131. package/wgx.mjs +290659 -0
@@ -0,0 +1,88 @@
1
+ # The local bulletin
2
+ <!-- group: Concepts -->
3
+
4
+ A board this machine keeps per repository, carrying every agent's task list.
5
+
6
+ Agents working the same codebase used to be invisible to each other. Two of
7
+ them given adjacent work would refactor the same file in opposite directions,
8
+ and neither ever learned why its edits kept coming back changed. The board is
9
+ the cheapest fix that does not require them to coordinate: everyone posts what
10
+ they are doing, anyone can read it, and nobody is obliged to.
11
+
12
+ ## What is on it
13
+
14
+ One block per agent, carrying that agent's current task list:
15
+
16
+ ```
17
+ AGENT N1 ACTIVE WORK:
18
+ 1. [x] Read XylonMountConfig for the file-watch shape
19
+ 2. [~] Wire the bulletin store into setSessionTodos
20
+ 3. [ ] Add api.read_bulletin and api.wait_for_task
21
+ ```
22
+
23
+ The list is exactly what that agent last sent to `todo_write` — or to the
24
+ native `TodoWrite` tool, which posts to the same place. Marking a task complete
25
+ updates the board; completing the last one takes the agent off it.
26
+
27
+ ## How you get on and off
28
+
29
+ You go **on** the first time you publish a list with anything unfinished. You
30
+ come **off** when every task is `completed`, or when your session ends.
31
+
32
+ So an agent that is not on the board is not necessarily idle — it may simply
33
+ never have published a list. Absence is not evidence, in either direction.
34
+
35
+ ## Its reach
36
+
37
+ Per **repository**, per **machine**. Two IDE windows open on the same repo see
38
+ each other; two different projects do not.
39
+
40
+ A forked session works in a clone of the repo, but it posts to the **origin's**
41
+ board rather than its clone's — otherwise the one agent most likely to collide
42
+ with you would be the one you could not see.
43
+
44
+ ## Reading it
45
+
46
+ `api.read_bulletin()` returns the whole board as text. `api.wait_for_task(node,
47
+ n)` blocks until another agent's task number `n` completes, and
48
+ `api.wait_for_task(node)` until its whole list is done.
49
+
50
+ Your session's system prompt says how many other agents were on the board when
51
+ you started. That number is a snapshot; the board itself is always current.
52
+
53
+ ## It is a hint, not a lock
54
+
55
+ Nothing here reserves a file, blocks a write, or fails a call. It exists so you
56
+ can notice a collision before it happens rather than after. There is no way to
57
+ claim anything and no way to stop anyone.
58
+
59
+ That is deliberate. A lock between agents needs a story for the agent that took
60
+ one and died, and this is not worth one.
61
+
62
+ ## Getting it wrong
63
+
64
+ **Treating it as a lock.** Two agents can edit one file at the same moment and
65
+ the board will not stop them. What it gives you is the chance to say "someone
66
+ is already inside this" before you start.
67
+
68
+ **Waiting on each other.** Two agents can `wait_for_task` on each other and both
69
+ sit until their timeouts. Nothing detects it — the bounded timeout is the only
70
+ thing that ends it, and the answer is not to wait for work you could do
71
+ yourself.
72
+
73
+ **Expecting a subagent to appear.** `agent` runs a subagent, not a session.
74
+ It cannot publish a task list, so the board shows *sessions*, and a busy
75
+ subagent shows up as its parent's entry or not at all.
76
+
77
+ **Believing a stale entry.** A window that crashed leaves its agents up until
78
+ they are reaped. They are checked against the process that wrote them, so this
79
+ resolves in seconds rather than minutes — but an agent that has published
80
+ nothing for a while is annotated with when it was last seen, and that is worth
81
+ reading.
82
+
83
+ **Never publishing.** If you do not call `todo_write`, nobody can see what you
84
+ are doing. That costs the other agents more than it costs you.
85
+
86
+ ## See also
87
+
88
+ `read_bulletin`, `wait_for_task`, `todo_write`, `relay`, `sessions`
@@ -0,0 +1,54 @@
1
+ # api.capture_page(path, dispatcher_id, session_id) -> dict
2
+ <!-- group: Working on a website -->
3
+
4
+ Freezes the driven page — scripts stripped, styles inlined — and writes it straight to `path`. Gated.
5
+
6
+ This is how a benchmark's fixture gets made, not a general-purpose save. A
7
+ frozen page has no script left to phone home or re-render itself, and its
8
+ styles are inlined so it renders the same offline as it did live. The write
9
+ happens host-side, not in your process: the page is reached over CDP, which
10
+ has no filesystem of its own, and a frozen document can run to megabytes —
11
+ round-tripping that much text back to you as a return value would be the
12
+ expensive way to do what a direct write does for free.
13
+
14
+ It is gated for the same reason `browser_snapshot` is: the frozen page is
15
+ whatever the human happens to be signed into, and writing that to disk is a
16
+ second reason on top of the first.
17
+
18
+ ## Options
19
+
20
+ - **`path`** — where to write the freeze, relative to the workspace root or
21
+ absolute. Parent directories are created as needed.
22
+ - **`dispatcher_id`** — the browser dispatcher to capture.
23
+ - **`session_id`** — used to find the dispatcher when `dispatcher_id` is
24
+ unknown. Usually left out; your own session's dispatcher is used.
25
+
26
+ ## Returns
27
+
28
+ `{path, url, title, nodes}` — never the frozen page itself. `nodes` is the
29
+ addressable-element count, which is what tells you the capture actually saw a
30
+ real page rather than a blank one.
31
+
32
+ ## Examples
33
+
34
+ ```python
35
+ await api.drive("https://example.com/pricing", why="freeze the pricing page")
36
+ await api.capture_page(".widgex/benchmarks/upgrade-plan/captures/1/pricing.json")
37
+ ```
38
+
39
+ ## Getting it wrong
40
+
41
+ **Reaching for this to save a screenshot.** `browser_screenshot` is the
42
+ picture; this is a structural freeze meant to be replayed, not looked at.
43
+
44
+ **Calling it before the page is where you mean it to be.** It captures
45
+ whatever is currently on screen — settle the page first (`api.wait_stable()`
46
+ or an explicit check) or you freeze a transition instead of a state.
47
+
48
+ **Using it outside a benchmark's own directory.** It writes anywhere `path`
49
+ names; nothing enforces `.widgex/benchmarks/`. That is deliberate — the
50
+ convention is yours to keep, not the call's to enforce.
51
+
52
+ ## See also
53
+
54
+ `browser_snapshot`, `browser_screenshot`, `drive`
@@ -0,0 +1,64 @@
1
+ # api.cd(path) -> str
2
+ <!-- group: Coding -->
3
+
4
+ Move the shell's working directory. Persists across execs. Not gated.
5
+
6
+ ## Options
7
+
8
+ - **`path`** — the new directory. Absolute, or relative to the *current* cwd.
9
+ `".."` works.
10
+
11
+ ## Returns
12
+
13
+ - `"Changed to {resolved_absolute_path}"` on success.
14
+ - `"Error: not a directory: {path}"` if the path is missing or is a file.
15
+ **This is a return, not a raise** — the cwd is unchanged and nothing tells
16
+ you unless you look.
17
+
18
+ ## What it affects
19
+
20
+ The new cwd is used by every subsequent relative path in the session, in this
21
+ exec and in every later one:
22
+
23
+ - `read_file`, `write_file`, `edit_file`, `ls`, `cd`
24
+ - the default `path` for `glob` and `grep`
25
+ - **the working directory `api.bash` runs commands in**
26
+
27
+ It does not affect anything outside the shell. The session's repo, the xylon
28
+ node and the browser are all unmoved.
29
+
30
+ ## Examples
31
+
32
+ ```python
33
+ return await api.cd("widgex-ide/extension")
34
+ ```
35
+
36
+ Check it landed:
37
+
38
+ ```python
39
+ r = await api.cd(target)
40
+ if r.startswith("Error:"):
41
+ return r
42
+ return await api.bash("pwd")
43
+ ```
44
+
45
+ ## Getting it wrong
46
+
47
+ **Ignoring the error return.** A failed `cd` leaves you in the old directory,
48
+ so the next twenty relative paths quietly resolve somewhere else. This is the
49
+ one thing that actually goes wrong with this call.
50
+
51
+ **Using it as a scope.** There is no "cd back" — it is a persistent change to
52
+ the whole session, not a block. If you need one command elsewhere, prefer
53
+ `api.bash("cd sub && ...")`, which is scoped to that command, or pass an
54
+ explicit `path=` to the call that needs it.
55
+
56
+ **Assuming it moves `api.bash`'s idea of `~` or `$PWD` for already-running
57
+ work.** It sets the cwd for commands started *after* it.
58
+
59
+ **Preferring it to absolute paths.** Most of the time an absolute path in the
60
+ one call that needs it is clearer and cannot leak into later execs.
61
+
62
+ ## See also
63
+
64
+ `bash`, `ls`, `glob`, `read_file`
@@ -0,0 +1,53 @@
1
+ # api.chess_move(piece, to, promotion=None) -> dict
2
+ <!-- group: Sessions -->
3
+ <!-- also: chess_state, chess -->
4
+
5
+ Play your move as Black in the user's `/chess` game. Never asks. Only while
6
+ the user has a game open with you in wgx, the terminal app; anywhere else both
7
+ calls are refused.
8
+
9
+ The user plays White on a board beside your chat. When they move you are told,
10
+ the way relay mail reaches you: at the end of a tool result if you are mid-turn,
11
+ or as a prompt if you are idle. Then it is your move.
12
+
13
+ ## Squares
14
+
15
+ `(row, col)`, each 0-7. Row 0 is White's back rank (rank 1) and row 7 is
16
+ yours (rank 8); col 0 is the a-file. So e7 is `(6, 4)` and e5 is `(4, 4)`. A
17
+ name like `"e7"` works anywhere a square does.
18
+
19
+ ## api.chess_state() -> list
20
+
21
+ Every piece on the board as a `(piece, row, col)` tuple, the piece named with
22
+ its colour: `B` is Black (you), `W` is White.
23
+
24
+ ```python
25
+ [("RookW", 0, 0), ("KnightW", 0, 1), …, ("PawnB", 6, 4), …, ("KingB", 7, 4), …]
26
+ ```
27
+
28
+ ## api.chess_move(piece, to, promotion=None)
29
+
30
+ - `piece` — the square of the Black piece to move, or a tuple straight out of
31
+ `chess_state()`. Given a tuple, the name is checked against the square.
32
+ - `to` — where it goes.
33
+ - `promotion` — `"Q"`, `"R"`, `"B"` or `"N"` for a pawn reaching row 0. A
34
+ Queen when omitted.
35
+
36
+ Castle by moving the King two squares: `chess_move("e8", "g8")`. En passant is
37
+ the pawn's diagonal step onto the empty square behind the pawn that just
38
+ double-stepped.
39
+
40
+ Returns `{ ok, move, check, over, status }`, where `move` is the move in long
41
+ algebraic (`Ng8-f6`, `e7xd6 e.p.`, `O-O`, `+` for check, `#` for mate).
42
+
43
+ **An illegal move raises**, saying why — no piece there, not yours, not how it
44
+ moves, leaves your King in check, not your turn, the game is over — and the
45
+ board is unchanged. Read the board again and choose another.
46
+
47
+ ## Example
48
+
49
+ ```python
50
+ board = await api.chess_state()
51
+ pawn = next(p for p in board if p == ("PawnB", 6, 4))
52
+ return await api.chess_move(pawn, (4, 4)) # e7-e5
53
+ ```
@@ -0,0 +1,89 @@
1
+ # api.children() -> list[dict]
2
+
3
+ <!-- group: Sessions -->
4
+
5
+ What you have running underneath you, right now. Never asks.
6
+
7
+ The session tree cannot answer this. `xylon_get_tree()` is a record of what
8
+ happened — every node, live and long finished, across every project — and this is
9
+ a question about what is happening. Filtering one to get the other is how a
10
+ session ends up reporting confidently that it has nothing running.
11
+
12
+ ## Returns
13
+
14
+ One dict per child:
15
+
16
+ ```python
17
+ {"kind": "delegate" | "extend" | "branch",
18
+ "id": str, "node": str | None, "sessionId": str | None,
19
+ "label": str, "status": str, "model": str | None, "branch": str | None,
20
+ "task": str | None, "startedAt": int | None,
21
+ "ahead": int, "dirty": int, "files": int}
22
+ ```
23
+
24
+ - **`kind='delegate'`** — an `agent(wait=True)` running in your shell. Always
25
+ `working`: one that has finished is gone from this list, because it no longer
26
+ exists anywhere. `node` and `sessionId` are null — it is not a session. `task`
27
+ is the opening line of what you asked it for.
28
+ - **`kind='extend'`** — a session sharing your directory.
29
+ - **`kind='issue'`** — an Issue you raised with `agent(raise_=True)`. Here
30
+ because you started it, not because it is underneath you: it stands beside
31
+ your work in the Issue list, and the user lands and retires it on its own
32
+ terms. It does not disappear when you do.
33
+ - **`kind='branch'`** — a session on a worktree and branch of its own.
34
+ `ahead`/`dirty`/`files` count what it has done since it started; all three are
35
+ zero for the other kinds.
36
+
37
+ ## Only one level
38
+
39
+ Your children, not theirs. A grandchild is its parent's business, and a flat list
40
+ mixing the two would make "how many do I have" ambiguous at exactly the moment
41
+ you are asking it. Reach further by asking a child through its own session.
42
+
43
+ ## What it does not tell you
44
+
45
+ What a child **said**. That is `session_join`, and for one still running it would
46
+ be half a turn. This answers what is true, not what was reported.
47
+
48
+ ## Examples
49
+
50
+ Answering the question directly:
51
+
52
+ ```python
53
+ kids = await api.children()
54
+ if not kids:
55
+ return "Nothing running."
56
+ return "\n".join(f"{c['kind']} {c['label']} — {c['status']}" for c in kids)
57
+ ```
58
+
59
+ Waiting for whatever is still going:
60
+
61
+ ```python
62
+ for c in await api.children():
63
+ if c["sessionId"]:
64
+ await api.session_join(c["sessionId"], timeout=600)
65
+ ```
66
+
67
+ Reporting what is ready for the user to merge:
68
+
69
+ ```python
70
+ ready = [c for c in await api.children()
71
+ if c["kind"] == "branch" and (c["files"] or c["dirty"])]
72
+ return f"{len(ready)} ready to merge: " + ", ".join(c["branch"] for c in ready)
73
+ ```
74
+
75
+ ## Getting it wrong
76
+
77
+ **Polling it in a loop.** It is cheap, but nothing changes between two reads a
78
+ second apart. `session_join` blocks properly.
79
+
80
+ **Reading `ahead` as "ready".** A branch agent with `ahead=0` and `dirty=4` has
81
+ done four files' worth of work and committed none of it, which still lands. The
82
+ count that answers "has it done anything" is `files`.
83
+
84
+ **Expecting a finished delegate.** They are forgotten on return. If you needed
85
+ its answer you already have it — `agent(wait=True)` gave it to you.
86
+
87
+ ## See also
88
+
89
+ `agent`, `session_join`, `sessions`, `xylon`
@@ -0,0 +1,14 @@
1
+ # api.close_task(task) -> dict
2
+
3
+ <!-- group: Sessions -->
4
+
5
+ Close a task. Only when the user asks for it.
6
+
7
+ With nothing unlanded, the task closes: its agents end, its worktree and branch
8
+ are removed. With unlanded work, the user is shown the choice — land it, keep
9
+ the branch, or discard it — and this returns straight away with
10
+ `{"asked": True}`. You never throw work away.
11
+
12
+ ## Returns
13
+
14
+ `{"closed": bool, "asked": bool, "why": str | None}`
@@ -0,0 +1,75 @@
1
+ # api.compact(rundown, next_step=None) -> dict
2
+ <!-- also: compaction, context, clear_context, rundown, context-window -->
3
+ <!-- group: Sessions -->
4
+
5
+ Clear your own context at a point you choose, keeping a brief rundown. Not gated.
6
+
7
+ A long conversation costs more with every turn, because every turn re-reads all
8
+ of it. WidgeX compacts at the share of the model's context window the user set
9
+ on Settings → Routing (33% unless they changed it); the terminal tool's
10
+ description gives the figure. Between turns WidgeX compacts by itself. Mid-turn
11
+ it cannot, so once its estimate of your context passes the line, one of your
12
+ tool results ends with a **context check**, naming the line, that asks you to
13
+ call this at the next convenient point.
14
+
15
+ ## What happens
16
+
17
+ 1. The call records your rundown and returns at once.
18
+ 2. You finish the step you are on and **end your turn**.
19
+ 3. The conversation continues in a fresh context holding:
20
+ - the WidgeX system prompt,
21
+ - your rundown,
22
+ - the last few lines of the conversation,
23
+ - a note on where the rest is.
24
+ 4. If you gave `next_step`, the fresh context starts on it straight away.
25
+ Otherwise nothing more runs until the next prompt.
26
+
27
+ Nothing is lost. The whole transcript stays on your node:
28
+
29
+ - `xylon_read_turns(".", offset=…)` pages through the prompts and replies.
30
+ - `xylon_search("words")` finds the turn where something was said.
31
+ - `xylon_read_messages(".")` returns everything, tool output included.
32
+
33
+ ## Arguments
34
+
35
+ - **`rundown`**: 5 to 10 short lines on where things stand: what was asked,
36
+ what is done or decided, what is in hand, and what is next. Name files and
37
+ functions, but do not paste code: the fresh context can read the files
38
+ itself. At most 4000 characters.
39
+ - **`next_step`**: the prompt the fresh context starts on, written to yourself
40
+ ("Run the extension tests and fix what fails"). Leave it out when the work is
41
+ finished and you are waiting on the user.
42
+
43
+ Returns `{"status": "scheduled", "message": …}`. Raises when the rundown is
44
+ empty or too long.
45
+
46
+ ## Example
47
+
48
+ ```python
49
+ return await api.compact(
50
+ "User asked for the export button to write CSV as well as JSON.\n"
51
+ "Done: exportCsv() in src/export/csv.ts, wired into ExportMenu.tsx.\n"
52
+ "Decided: quote every field, CRLF line endings (Excel).\n"
53
+ "In hand: test/export.test.mjs has two failing cases for embedded quotes.\n"
54
+ "Next: fix the quoting in exportCsv and rerun that file.",
55
+ next_step="Fix the embedded-quote cases in exportCsv and rerun test/export.test.mjs.",
56
+ )
57
+ ```
58
+
59
+ ## Getting it wrong
60
+
61
+ **Compacting in the middle of an edit.** The fresh context only knows what the
62
+ rundown and the last few lines say. Finish the edit, or at least the file, first.
63
+
64
+ **A rundown that is a summary.** Ten paragraphs spend the space this frees.
65
+ Write where things stand; the transcript has the rest.
66
+
67
+ **Carrying on after the call.** The compaction happens when your turn ends.
68
+ Work done after it is in the context being cleared, and only the last few lines
69
+ of it survive.
70
+
71
+ **Asking the user to repeat themselves afterwards.** Read the transcript instead.
72
+
73
+ ## See also
74
+
75
+ `xylon_search`, `xylon_read`, `xylon_blame`
@@ -0,0 +1,140 @@
1
+ # api.drive(url, why, new_tab=False) -> dict
2
+ <!-- group: Working on a website -->
3
+
4
+ Drive a site by hand, with no widget. Gated.
5
+
6
+ The exception, not the default. Use it when `api.widgets()` has nothing for the
7
+ site. Every action is then a raw L1 call against whatever the page happens to
8
+ look like, so `why` is the only record of intent there will be — and it is what
9
+ the user sees when deciding whether to allow it.
10
+
11
+ It is also how you look at a page you have just written. `file://` is an
12
+ accepted scheme and a dev server is an ordinary `http://` URL, so drive then
13
+ `browser_screenshot` and you can see what you made instead of reasoning about
14
+ clip-path coordinates in your head.
15
+
16
+ ## Options
17
+
18
+ - **`url`** — where to go. One of:
19
+ - `https://` or `http://` — any host, including `http://localhost:5173/` and
20
+ `http://127.0.0.1:8080/`. A dev server needs nothing special.
21
+ - `file://` — a page on disk.
22
+ - an **absolute** path, which becomes its `file://` URL for you. Relative
23
+ paths are refused: `docs/index.html` and a bare hostname are the same
24
+ string.
25
+
26
+ Anything else — `data:`, `javascript:`, `chrome://`, `about:blank`, a bare
27
+ domain — raises before the prompt. Those are ways of making a page run
28
+ something, or the browser's own furniture, rather than places to go.
29
+ - **`why`** — **required.** What you are there to do, in one line. An empty
30
+ string raises with the reason: on a site nothing models, nothing else can
31
+ tell. A page on your own disk is no more self-explaining than one on the web.
32
+ - **`new_tab`** — `False` by default: you have one tab, and each drive moves it
33
+ to the new address. Pass `True` to open the page beside the one you are on
34
+ instead, when you genuinely need both. Opening tabs is deliberate, never a
35
+ habit: every one lands in the user's tab strip.
36
+
37
+ ## Your tab, and the user's
38
+
39
+ You work in the background. Your tab opens behind whatever the user is doing
40
+ and is never brought in front of them; they can look at it whenever they like,
41
+ and looking does not take it from you.
42
+
43
+ - **They worked in it** — clicked, scrolled or typed in the page — so it is
44
+ theirs. Your next drive somewhere else opens a new tab and leaves theirs
45
+ where they left it. Picking the tab in the strip just to look does not count.
46
+ - **You need them** — a form to submit, a sign-in, a CAPTCHA: fill in what you
47
+ can, then `api.request_operator_action(action, detail)`. That hands them this
48
+ tab; their card has an Open button that brings it up exactly as you left it.
49
+ You will not navigate a handed tab again — your next drive opens a new one
50
+ beside it — and it stays open after you finish, so the card still works.
51
+
52
+ A browser specialist you hand work to gets one tab of its own, so several
53
+ running at once do not drive each other's pages.
54
+
55
+ A `file://` target that does not exist raises `FileNotFoundError` **before** the
56
+ permission prompt, rather than costing you a tab and a screenshot of Chromium's
57
+ error page.
58
+
59
+ ## Returns
60
+
61
+ ```python
62
+ {"url": str, "state": str, "why": str}
63
+ ```
64
+
65
+ `state` is `"UnknownPage"` unless a loaded widget happened to match.
66
+
67
+ ## Examples
68
+
69
+ A site with no model:
70
+
71
+ ```python
72
+ return await api.drive(
73
+ "https://status.example.com/incidents/4821",
74
+ why="read the incident timeline the user linked, to date the outage",
75
+ )
76
+ ```
77
+
78
+ Looking at markup you have just written:
79
+
80
+ ```python
81
+ out = f"{api.cwd}/.widgex-scratch/animation.html"
82
+ await api.write_file(out, html)
83
+ await api.drive(out, why="check the panel's entry animation before shipping it")
84
+ shot = await api.browser_screenshot()
85
+ ```
86
+
87
+ A dev server, which was never a special case:
88
+
89
+ ```python
90
+ await api.drive("http://localhost:5173/settings", why="confirm the new tab renders")
91
+ ```
92
+
93
+ Falling back only after checking:
94
+
95
+ ```python
96
+ found = await api.widgets(url)
97
+ if any(w["matches"] for w in found):
98
+ return await api.use_widget(next(w["name"] for w in found if w["matches"]))
99
+ return await api.drive(url, why="download the CSV export the user asked for")
100
+ ```
101
+
102
+ Then working the page, with the verbs in this shell — see `driving`:
103
+
104
+ ```python
105
+ print(await api.outline()) # the shape of the page
106
+ hits = await api.search(role="button", like="export")
107
+ api.click(api.find(role="button", like="export"))
108
+ print(await api.drain())
109
+ ```
110
+
111
+ ## Getting it wrong
112
+
113
+ **Using it when a widget exists.** `api.widgets()` costs nothing. Driving past a
114
+ model throws away tested transitions and shows the user an anonymous click.
115
+
116
+ **A vague `why`.** "look at a page" is the whole explanation the user gets for
117
+ an unmodelled site. Say what you are there to do.
118
+
119
+ **Passing a bare domain.** No scheme, no call — it raises before prompting.
120
+ `example.com` is indistinguishable from a relative path, so neither is guessed.
121
+
122
+ **Standing up a web server to show yourself one file.** That was the only route
123
+ before `file://` was accepted, and it is three steps you no longer need. Write
124
+ the file, drive its path.
125
+
126
+ **Reading the CSS instead of looking at the page.** The reason this capability
127
+ exists: a screenshot shows an animation is wrong in ways the source does not.
128
+
129
+ **Expecting `state` to be useful.** It is `UnknownPage` in the normal case.
130
+ There is no state machine here; that is what "no widget" means.
131
+
132
+ **Driving the same site repeatedly.** That site wants a widget. Say so.
133
+
134
+ **Using a screenshot to locate elements.** An image cannot be searched.
135
+ `browser_hybrid` returns structure.
136
+
137
+ ## See also
138
+
139
+ `driving`, `widgets`, `use_widget`, `widget-model`, `browser_hybrid`,
140
+ `browser_screenshot`
@@ -0,0 +1,72 @@
1
+ # api.drive_tree / drive_import / drive_push / drive_review — drive spaces
2
+ <!-- also: drive_tree, drive_import, drive_push, drive_review, drive_review_status, drive_spaces, onedrive, google_drive, gdrive -->
3
+ <!-- group: Coding -->
4
+
5
+ A **drive space** is a OneDrive or Google Drive folder opened as the workspace.
6
+ The person using it is usually not a developer and never sees git. What matters
7
+ to them is their documents, and what reaches their drive.
8
+
9
+ These calls exist only in a drive space, or in a task cut from one. Anywhere
10
+ else they say so and do nothing.
11
+
12
+ ## Two places to stand
13
+
14
+ **In the space itself** (the Coordinator, or any main agent) you have the
15
+ folder's *listing* and none of its bytes. Your system prompt carries the listing
16
+ as it was when your session started. Files arrive when you import them.
17
+
18
+ ```python
19
+ await api.drive_tree("Reports", depth=1) # what is there now
20
+ await api.drive_import("Reports/Q3.docx") # fetch it, to the same path
21
+ # ... edit Reports/Q3.docx with python-docx ...
22
+ await api.drive_push(["Reports/Q3.docx"], "Updated the Q3 totals")
23
+ return "Asked you to approve the Q3 report changes."
24
+ ```
25
+
26
+ **In a task** you have a full copy of one sub-folder, chosen by the user when
27
+ the task started. Edit freely, then ask for review once:
28
+
29
+ ```python
30
+ await api.drive_review("Rewrote the summary and fixed the tables")
31
+ return "Your changes are open for review in the browser tabs."
32
+ ```
33
+
34
+ ## Nothing reaches the drive without approval
35
+
36
+ `drive_push` and `drive_review` **do not block and publish nothing by
37
+ themselves.** Each changed file opens in its own browser tab, rendered — a PDF as
38
+ pages, a Word document as a document — with "Approve all & publish" and "Request
39
+ changes" on every tab.
40
+
41
+ - **Approve** publishes the whole set and **overwrites** the drive's copies. A
42
+ task's changes are committed first. The overwritten versions stay in the
43
+ drive's own version history.
44
+ - **Request changes** publishes nothing and sends you the user's note as a new
45
+ message.
46
+
47
+ Either way you are told in your own conversation. So the call is the **end** of
48
+ a turn: say what you are waiting on and stop. `drive_review_status(review_id,
49
+ wait_seconds)` exists for checking, not for polling.
50
+
51
+ ## The calls
52
+
53
+ - `drive_tree(path='', depth=None) -> {drive, folder, rows, text}` — the listing
54
+ now. Rows carry `folder`, `size`, `readOnly` and `imported`. Not gated.
55
+ - `drive_import(paths) -> {imported, kept, skipped}` — one path or a list, file or
56
+ folder. A file you imported and since edited is **kept**, not overwritten.
57
+ Gated: it downloads and writes files.
58
+ - `drive_push(paths, message=None) -> {review_id, files, state}` — from the space.
59
+ - `drive_review(message=None) -> {review_id, files, state}` — from a task.
60
+ - `drive_review_status(review_id, wait_seconds=0)` — `open`, `publishing`,
61
+ `approved`, `rejected` or `failed`, with what was published or refused.
62
+
63
+ ## Getting it wrong
64
+
65
+ **Polling after asking.** You are told. Ending the turn is the right move.
66
+
67
+ **Writing to an exported Google document.** Docs, Sheets and Slides arrive as
68
+ `.docx`, `.xlsx` and `.pptx` exports marked read-only. They can be read; a push
69
+ of one is refused by name and the rest of the set still publishes.
70
+
71
+ **Importing a whole folder to edit one file.** Import the file. A task is the
72
+ tool for working across a folder — it gets the whole thing.