pycodex-ws 0.5.0__tar.gz → 0.5.2__tar.gz

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 (101) hide show
  1. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/PKG-INFO +42 -3
  2. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/README.md +40 -1
  3. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_ws/feishu_card.py +2 -2
  4. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_ws/feishu_link.py +1 -1
  5. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pyproject.toml +2 -2
  6. pycodex_ws-0.5.2/workspace_server/WORKSPACE_API.md +257 -0
  7. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/app.py +423 -47
  8. pycodex_ws-0.5.2/workspace_server/content_views.css +1 -0
  9. pycodex_ws-0.5.2/workspace_server/content_views.mjs +32 -0
  10. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/customization.py +1 -1
  11. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/composer.mjs +28 -2
  12. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/content_views.css +33 -3
  13. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/content_views.mjs +73 -3
  14. pycodex_ws-0.5.2/workspace_server/frontend/prompt_history.mjs +85 -0
  15. pycodex_ws-0.5.2/workspace_server/optional_features/CONTRIBUTING.md +157 -0
  16. pycodex_ws-0.5.2/workspace_server/optional_features/README.md +27 -0
  17. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/README.md +118 -0
  18. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/preview-mobile.png +0 -0
  19. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/preview.html +48 -0
  20. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/preview.png +0 -0
  21. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/source/fixture.mjs +11 -0
  22. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/source/tool-groups.css +11 -0
  23. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/source/tool-groups.js +111 -0
  24. pycodex_ws-0.5.2/workspace_server/optional_features/consecutive-tool-groups/source/verify.mjs +61 -0
  25. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/README.md +85 -0
  26. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/preview-mobile.png +0 -0
  27. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/preview.html +33 -0
  28. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/preview.png +0 -0
  29. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/source/custom.css +5 -0
  30. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/source/custom.js +75 -0
  31. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/source/fixture.mjs +18 -0
  32. pycodex_ws-0.5.2/workspace_server/optional_features/exec-status/source/verify.mjs +80 -0
  33. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/README.md +88 -0
  34. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/preview-mobile.png +0 -0
  35. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/preview.html +34 -0
  36. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/preview.png +0 -0
  37. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/source/custom.css +6 -0
  38. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/source/custom.js +95 -0
  39. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/source/fixture.mjs +26 -0
  40. pycodex_ws-0.5.2/workspace_server/optional_features/reasoning-display/source/verify.mjs +80 -0
  41. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/README.md +126 -0
  42. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/preview-mobile.png +0 -0
  43. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/preview.html +57 -0
  44. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/preview.png +0 -0
  45. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/source/custom.css +46 -0
  46. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/source/custom.js +49 -0
  47. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/source/fixture.mjs +25 -0
  48. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/source/session-tips.js +165 -0
  49. pycodex_ws-0.5.2/workspace_server/optional_features/session-tips/source/verify.mjs +71 -0
  50. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/README.md +87 -0
  51. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/preview-mobile.png +0 -0
  52. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/preview.html +33 -0
  53. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/preview.png +0 -0
  54. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/source/custom.css +8 -0
  55. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/source/custom.js +92 -0
  56. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/source/fixture.mjs +22 -0
  57. pycodex_ws-0.5.2/workspace_server/optional_features/subagent-status/source/verify.mjs +80 -0
  58. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/README.md +92 -0
  59. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/preview-mobile.png +0 -0
  60. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/preview.html +34 -0
  61. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/preview.png +0 -0
  62. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/source/custom.css +6 -0
  63. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/source/custom.js +112 -0
  64. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/source/fixture.mjs +25 -0
  65. pycodex_ws-0.5.2/workspace_server/optional_features/tool-calls/source/verify.mjs +80 -0
  66. pycodex_ws-0.5.2/workspace_server/session_api.py +142 -0
  67. pycodex_ws-0.5.2/workspace_server/sharing.py +93 -0
  68. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/workspace.html +580 -49
  69. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/workspace_views.py +19 -6
  70. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/workspaces.py +23 -2
  71. pycodex_ws-0.5.0/workspace_server/content_views.css +0 -1
  72. pycodex_ws-0.5.0/workspace_server/content_views.mjs +0 -31
  73. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/.gitignore +0 -0
  74. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/LICENSE +0 -0
  75. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_router/__init__.py +0 -0
  76. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_router/__main__.py +0 -0
  77. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_router/browser.py +0 -0
  78. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_router/client.py +0 -0
  79. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_router/index.html +0 -0
  80. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_router/protocol.py +0 -0
  81. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_router/server.py +0 -0
  82. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_ws/__init__.py +0 -0
  83. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/pycodex_ws/feishu_oauth.py +0 -0
  84. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/__init__.py +0 -0
  85. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/__main__.py +0 -0
  86. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/agent_access.py +0 -0
  87. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/content_views.LICENSE.txt +0 -0
  88. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/editor.css +0 -0
  89. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/editor.mjs +0 -0
  90. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/.gitignore +0 -0
  91. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/build.mjs +0 -0
  92. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/editor.css +0 -0
  93. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/editor.mjs +0 -0
  94. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/editor_context.mjs +0 -0
  95. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/package-lock.json +0 -0
  96. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/package.json +0 -0
  97. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/touch_selection.mjs +0 -0
  98. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/frontend/view_labels.mjs +0 -0
  99. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/owned_process.py +0 -0
  100. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/runtime_info.py +0 -0
  101. {pycodex_ws-0.5.0 → pycodex_ws-0.5.2}/workspace_server/workspaces.html +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycodex-ws
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: Browser workspace and optional Feishu integration for python-codex
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.7
7
7
  Requires-Dist: pillow>=9.0.0
8
- Requires-Dist: python-codex==0.5.0
8
+ Requires-Dist: python-codex==0.5.2
9
9
  Requires-Dist: websockets<16,>=11
10
10
  Description-Content-Type: text/markdown
11
11
 
@@ -52,6 +52,12 @@ running sessions and unread replies. `/cd <directory>` changes the current
52
52
  session's working directory while idle; `/model`, `/resume`, `/compact` and
53
53
  other core session commands are also available.
54
54
 
55
+ Consecutive model reconnection diagnostics share one collapsed `Model retries`
56
+ row per sequence. The summary shows the number of recorded retries, the latest
57
+ attempt and its HTTP status when available. Expand it to read the original errors
58
+ in a scrollable log. New retries update the same row and preserve its open state;
59
+ refreshing starts collapsed. Other model diagnostics and final errors stay visible.
60
+
55
61
  Click Markdown links to open files and comparisons, for example
56
62
  `[source](app.py:42)` or `[changes](diff:HEAD:app.py)`. In a code or diff view,
57
63
  select text and choose **Insert into chat** to add editable context to the draft.
@@ -67,7 +73,8 @@ distributed separately as a VSIX.
67
73
 
68
74
  ## Customize the interface
69
75
 
70
- Click the gear button to start a customization conversation with the Agent.
76
+ Click the gear button and choose **Frontend customization** to start a
77
+ customization conversation with the Agent.
71
78
  Custom files live in the server user's `~/.pycodex/frontend/`:
72
79
 
73
80
  - `custom.css` and `custom.js`: optional styles and an ES module, loaded automatically.
@@ -78,6 +85,38 @@ Refresh to apply changes and keep the selected conversation. Open the workspace
78
85
  with `?builtin=1` to bypass all customizations; click “Re-enable customizations”
79
86
  to return. Changes apply to all workspaces for that OS user.
80
87
  See the [customization contract](../../docs/RUNTIME.md#frontend-customization).
88
+ For custom interfaces, the [resource API](../../docs/WORKSPACE_API.md) exposes
89
+ state, model catalogs, asynchronous command results, paginated history, event
90
+ replay and sub-agent/background-work inventories. A `mount(host)` export in
91
+ `custom.js` can access the built-in page's session, draft and view controls.
92
+
93
+ Ask the Agent to show the [feature catalog](workspace_server/optional_features/README.md)
94
+ in the view area, then choose features by name or ID in chat. The Agent adapts
95
+ their source into your frontend. To share a local change, ask it to publish a
96
+ feature PR on GitHub; the [contribution guide](workspace_server/optional_features/CONTRIBUTING.md)
97
+ specifies the source, documentation, validation and PR format.
98
+
99
+ ## Share a workspace
100
+
101
+ Start the workspace server with an administrator password (`--password`), then
102
+ choose **Settings → Share** in a workspace. Set a different share password and
103
+ send the displayed workspace link and password to the recipient.
104
+
105
+ The existing login page accepts either the administrator password or the share
106
+ password for the workspace being opened. Visitors can use all its conversations,
107
+ Canvas, files, APIs and WebSocket connections, but cannot access workspace
108
+ management, another workspace or sharing settings. The settings button and
109
+ management navigation are hidden for visitors.
110
+
111
+ Passwords, verification values and visitor tokens are kept only in process
112
+ memory. Changing the password or stopping sharing invalidates existing visitor
113
+ logins and disconnects their WebSockets. Restarting the process clears all
114
+ shares. Already accepted Agent work continues.
115
+
116
+ This restricts web access; Agent tools retain their existing file and execution
117
+ permissions. An unprotected server must configure its administrator password
118
+ before enabling sharing. Router authentication is unchanged: a link through a
119
+ router still requires its normal login.
81
120
 
82
121
  ## Storage
83
122
 
@@ -41,6 +41,12 @@ running sessions and unread replies. `/cd <directory>` changes the current
41
41
  session's working directory while idle; `/model`, `/resume`, `/compact` and
42
42
  other core session commands are also available.
43
43
 
44
+ Consecutive model reconnection diagnostics share one collapsed `Model retries`
45
+ row per sequence. The summary shows the number of recorded retries, the latest
46
+ attempt and its HTTP status when available. Expand it to read the original errors
47
+ in a scrollable log. New retries update the same row and preserve its open state;
48
+ refreshing starts collapsed. Other model diagnostics and final errors stay visible.
49
+
44
50
  Click Markdown links to open files and comparisons, for example
45
51
  `[source](app.py:42)` or `[changes](diff:HEAD:app.py)`. In a code or diff view,
46
52
  select text and choose **Insert into chat** to add editable context to the draft.
@@ -56,7 +62,8 @@ distributed separately as a VSIX.
56
62
 
57
63
  ## Customize the interface
58
64
 
59
- Click the gear button to start a customization conversation with the Agent.
65
+ Click the gear button and choose **Frontend customization** to start a
66
+ customization conversation with the Agent.
60
67
  Custom files live in the server user's `~/.pycodex/frontend/`:
61
68
 
62
69
  - `custom.css` and `custom.js`: optional styles and an ES module, loaded automatically.
@@ -67,6 +74,38 @@ Refresh to apply changes and keep the selected conversation. Open the workspace
67
74
  with `?builtin=1` to bypass all customizations; click “Re-enable customizations”
68
75
  to return. Changes apply to all workspaces for that OS user.
69
76
  See the [customization contract](../../docs/RUNTIME.md#frontend-customization).
77
+ For custom interfaces, the [resource API](../../docs/WORKSPACE_API.md) exposes
78
+ state, model catalogs, asynchronous command results, paginated history, event
79
+ replay and sub-agent/background-work inventories. A `mount(host)` export in
80
+ `custom.js` can access the built-in page's session, draft and view controls.
81
+
82
+ Ask the Agent to show the [feature catalog](workspace_server/optional_features/README.md)
83
+ in the view area, then choose features by name or ID in chat. The Agent adapts
84
+ their source into your frontend. To share a local change, ask it to publish a
85
+ feature PR on GitHub; the [contribution guide](workspace_server/optional_features/CONTRIBUTING.md)
86
+ specifies the source, documentation, validation and PR format.
87
+
88
+ ## Share a workspace
89
+
90
+ Start the workspace server with an administrator password (`--password`), then
91
+ choose **Settings → Share** in a workspace. Set a different share password and
92
+ send the displayed workspace link and password to the recipient.
93
+
94
+ The existing login page accepts either the administrator password or the share
95
+ password for the workspace being opened. Visitors can use all its conversations,
96
+ Canvas, files, APIs and WebSocket connections, but cannot access workspace
97
+ management, another workspace or sharing settings. The settings button and
98
+ management navigation are hidden for visitors.
99
+
100
+ Passwords, verification values and visitor tokens are kept only in process
101
+ memory. Changing the password or stopping sharing invalidates existing visitor
102
+ logins and disconnects their WebSockets. Restarting the process clears all
103
+ shares. Already accepted Agent work continues.
104
+
105
+ This restricts web access; Agent tools retain their existing file and execution
106
+ permissions. An unprotected server must configure its administrator password
107
+ before enabling sharing. Router authentication is unchanged: a link through a
108
+ router still requires its normal login.
70
109
 
71
110
  ## Storage
72
111
 
@@ -357,9 +357,9 @@ class PycodexCard:
357
357
  or getattr(action_payload, "input_value", None)
358
358
  or value.get("input_value")
359
359
  or ""
360
- ).strip()
360
+ )
361
361
  if not prompt and form_values:
362
- prompt = str(next(iter(form_values.values()))).strip()
362
+ prompt = str(next(iter(form_values.values())))
363
363
  action = str(
364
364
  value.get("action") or getattr(action_payload, "name", None) or "send"
365
365
  ).strip()
@@ -105,7 +105,7 @@ class PycodexRuntimeLink:
105
105
  "content": "Unsupported action: {0}".format(action.get("action")),
106
106
  }
107
107
  }
108
- prompt = str(action.get("prompt") or "").strip()
108
+ prompt = str(action.get("prompt") or "")
109
109
  future = asyncio.run_coroutine_threadsafe(
110
110
  self.runtime.submit_input(
111
111
  prompt, sender=str(action.get("sender") or "feishu")
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pycodex-ws"
7
- version = "0.5.0"
7
+ version = "0.5.2"
8
8
  description = "Browser workspace and optional Feishu integration for python-codex"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
11
11
  dependencies = [
12
- "python-codex==0.5.0",
12
+ "python-codex==0.5.2",
13
13
  "pillow>=9.0.0",
14
14
  "websockets>=11,<16",
15
15
  ]
@@ -0,0 +1,257 @@
1
+ # Workspace resource API
2
+
3
+ This reference ships with `pycodex-ws`, beside `workspace.html`. The interface
4
+ customization prompt includes its absolute path so the Agent can read the
5
+ installed API contract without a repository checkout.
6
+
7
+ Custom frontends use the same authenticated backend as the built-in workspace.
8
+ Paths below are workspace-relative: use `api/...` from its page, or prefix with
9
+ `/w/{workspace_id}/` on a multi-workspace server. Existing endpoints remain
10
+ available. Resource responses include `api_version: 1` and use
11
+ `Cache-Control: no-store`.
12
+
13
+ ## Workspace sharing
14
+
15
+ `GET api/share`, `PUT api/share` and `DELETE api/share` require the server's
16
+ administrator identity. PUT takes `{"password": "..."}`; the password must be
17
+ nonempty and differ from the configured workspace server administrator password.
18
+ Sharing is unavailable when that administrator password is unset.
19
+
20
+ The response is `{enabled, available, url}`. `url` is the workspace's existing
21
+ page path including any mount prefix. GET never returns a password or token.
22
+ DELETE disables sharing; PUT rotates its password and visitor grant.
23
+ These endpoints require a JSON object for PUT and return `Cache-Control: no-store`.
24
+
25
+ `POST /login?next=<workspace-path>` reuses the normal login form. The administrator
26
+ password grants administrator access; a matching share password grants access
27
+ only to that workspace. The server checks the cookie's grant on every HTTP and
28
+ WebSocket request. Visitor login redirects are restricted to the granted
29
+ workspace. Passwords and tokens have no persistence, and password changes,
30
+ revocation or process restart invalidate old grants. Revocation closes existing
31
+ visitor WebSockets without cancelling accepted Agent work.
32
+
33
+ The rendered page has `data-access-role="admin"` or `"visitor"` on its body.
34
+ Custom frontends can use `__ACCESS_ROLE__` to hide management controls; authorization
35
+ is enforced on the server regardless of UI visibility. Sharing does not reduce
36
+ Agent execution permissions, and does not replace Router authentication.
37
+
38
+ ## Creating sessions
39
+
40
+ `GET api/sessions` returns `{sessions, profiles, default_profile}`. `profiles`
41
+ contains only names from the server's Codex config; no provider configuration
42
+ or credentials are returned. The catalog is loaded when the workspace is built.
43
+ `default_profile` is the launch `--profile`, or `null` for the base configuration.
44
+ Each session list entry includes its effective `profile` name or `null`.
45
+
46
+ `POST api/sessions` accepts `{title?, prompt?, profile?}`. Title and prompt are
47
+ strings and default to empty. Profile is a string name; omission or `null` uses
48
+ the server default. An unknown name returns HTTP 400; an invalid field type
49
+ returns HTTP 422. An empty body still creates a default session.
50
+ The selected profile configures both the model and Agent instructions; CLI
51
+ overrides still apply. A nonempty prompt is submitted through Runtime.
52
+ The response contains `ok`, `session_id`, `sessions`, and `snapshot`, plus the
53
+ submission receipt when a prompt was supplied.
54
+
55
+ The saved tab stores the selected name, including an explicit `null` for the
56
+ base configuration. Restart resolves that name against the current config,
57
+ even when the launch default changed. Older tabs without a profile use the
58
+ launch default. A missing saved profile fails restoration. Existing sessions
59
+ keep their configuration; this API does not switch profiles in a running tab.
60
+
61
+ ## PDF previews
62
+
63
+ `POST api/views/open` with `{"href": "report.pdf"}` opens a local PDF using the
64
+ normal file-view path resolution. Its document has `media_type: "application/pdf"`
65
+ and source metadata, without text or embedded file bytes.
66
+ `GET/HEAD api/views/{id}/pdf` streams that view's current file with
67
+ `Content-Type: application/pdf` and inline disposition; `?download=true`
68
+ selects attachment disposition. These routes require workspace authentication.
69
+ Closing the view removes access through that ID. They accept no file-path input.
70
+ PDF files do not use the text/image snapshot size limit; invalid headers are
71
+ rejected. `revision` in the iframe URL changes when file metadata changes.
72
+
73
+ ## State and resources
74
+
75
+ | Method and path | Response fields |
76
+ | --- | --- |
77
+ | `GET api/sessions/{id}/state` | `state`, `cursor` |
78
+ | `GET api/sessions/{id}/models` | `model`, `models` |
79
+ | `GET api/sessions/{id}/history?limit=100&before=...` | `source`, `items`, `next_cursor` |
80
+ | `GET api/sessions/{id}/agents` | `agents` |
81
+ | `GET api/sessions/{id}/background-work` | `background_work` |
82
+ | `POST api/sessions/{id}/commands` | `submission_id`, `submission` |
83
+ | `GET api/sessions/{id}/submissions/{submission_id}` | `submission` |
84
+
85
+ `id` is the tab ID from `GET api/sessions`. It remains stable when `/fork` or
86
+ `/resume` changes the Agent identity. `state.agent_session_id` identifies the
87
+ current Agent/rollout session.
88
+
89
+ State includes:
90
+
91
+ - `model: {name, reasoning_effort}`, `title`, and `cwd`;
92
+ - `busy`, `running`, `closed`, and `accepts_input`;
93
+ - `active_turn`: IDs, user texts, accumulated assistant text and model errors,
94
+ or `null`; completed history is excluded;
95
+ - `queued_inputs`: `submission_id`, `turn_id`, `queue` (`steer` or `enqueue`)
96
+ and `prompts`, in execution order; merged steer inputs share one ID;
97
+ - `commands`: currently queued/running commands with their submission IDs;
98
+ - `input_request`, `plan`, and `background_work_count`;
99
+ - `context_window`, `max_context_window`, `auto_compact_token_limit`,
100
+ and the latest `usage_tokens`.
101
+
102
+ `running` describes Agent execution; `busy` additionally includes queued turns
103
+ and draining. Command status is separate, so a custom command can run while the
104
+ Agent is idle. State/model queries do not add control messages or invoke a model
105
+ completion.
106
+ Workspace model clients implement `ModelControl`, including an explicit
107
+ `reasoning_effort` (possibly `None`) and `list_models()`. The core Agent's
108
+ minimal `ModelClient` protocol is unchanged.
109
+
110
+ `agents` is a flattened descendant tree with `agent_id`, `parent_agent_id`,
111
+ `nickname`, and the existing sub-agent `status` value. The root is identified
112
+ by `state.agent_session_id`. Descendant IDs are assigned by their manager and
113
+ remain stable across that child's fork/resume. Add `?agent_id=...` to state,
114
+ models, history, agents, background-work or commands to target an owned child.
115
+
116
+ Background records have `tool` and `kind`. Exec records include `session_id`,
117
+ `pid`, `tty`, `status` (`running` or `exited`) and `exit_code`. They omit shell
118
+ arguments, environment and process output. Entries disappear when the exec
119
+ manager releases them; this is an inventory, not an execution archive.
120
+ Clock records include `enabled`, `period_m`, `status` (`disabled`, `paused`,
121
+ or `armed`) and timezone-aware `next_fire_at` (`null` when not armed).
122
+
123
+ ## Commands and results
124
+
125
+ Commands use a name without the slash and an optional string argument:
126
+
127
+ ```js
128
+ const response = await fetch(`api/sessions/${sessionId}/commands`, {
129
+ method: "POST",
130
+ headers: {"Content-Type": "application/json"},
131
+ body: JSON.stringify({command: "effort", argument: "high"}),
132
+ });
133
+ if (!response.ok) throw new Error((await response.json()).detail);
134
+ const {submission_id} = await response.json();
135
+ const result = await fetch(
136
+ `api/sessions/${sessionId}/submissions/${submission_id}`,
137
+ ).then(response => response.json());
138
+ ```
139
+
140
+ Accepted requests return HTTP **202**, including fast commands. Records contain
141
+ `submission_id`, `agent_id`, `kind`, `status`, `result`, and `error`; commands
142
+ also have `command`. Status progresses through `queued`, `running`, then
143
+ `completed` or `failed`. Failures have `error: {type, message}`. Query until
144
+ terminal or consume WS `submission` notifications. A 202 receipt does not imply
145
+ execution succeeded.
146
+
147
+ Names come from Runtime's command registry, including workspace commands.
148
+ `queue` is excluded here: send `/queue <message>` through the message endpoint.
149
+ Operations retain their idle/admission requirements. Runtime owns serialization
150
+ and draining. HTTP request completion or disconnect does not cancel an accepted
151
+ command. This adds no turn cancellation, queue editing or arbitrary tool execution.
152
+
153
+ Regular workspace message receipts are also queryable. Their successful result
154
+ contains `turn_id`, `output_text`, and `iterations`. Command events observed
155
+ from other frontends are retained as well. Retention is **all pending submissions
156
+ plus the last 500 completed submissions** in the workspace session's memory,
157
+ shared across its observed agents. Restart/deletion clears this store;
158
+ expired/unknown IDs return 404. Submission lookup does not need `agent_id`.
159
+
160
+ Malformed input returns 422, missing resources return 404, and admission rejected
161
+ because the Agent is shutting down returns 409. Execution failures after
162
+ admission appear in the submission record.
163
+
164
+ ## History pagination
165
+
166
+ `limit` is between 1 and 200. The initial page contains the newest records;
167
+ each page is chronological. Pass `next_cursor` as `before` for older records;
168
+ stop at `next_cursor: null`. Cursors are opaque.
169
+
170
+ Items contain `index`, `type`, and `payload`. A `response_item` carries the
171
+ serialized conversation item, including tool calls/results and available
172
+ reasoning records. This does not decrypt reasoning or reconstruct unrecorded
173
+ output. There is currently no dedicated live reasoning-delta event; the display
174
+ snapshot's `thinking` field contains assistant text and is not a reasoning API.
175
+ A `compacted` item contains its summary in `payload.message`; it does
176
+ not duplicate the checkpoint's replacement history.
177
+
178
+ Recorded sessions use `source: "rollout"` and retain pre-compaction records.
179
+ Session metadata and duplicate display events are excluded. Reading uses bounded
180
+ page memory and scans the rollout up to the requested record; no separate
181
+ database/index is maintained. File I/O runs off the session loop.
182
+
183
+ Unrecorded sessions and forks before their first write use
184
+ `source: "active_history"`; only current in-memory history is available.
185
+ Appending preserves older cursors. Compaction, a changed session/source, or
186
+ the transition from unwritten to recorded history can invalidate a cursor;
187
+ HTTP 409 directs the client to restart pagination. Sources are append-only;
188
+ external rollout edits are not a supported pagination workflow.
189
+
190
+ ## WebSocket state and replay
191
+
192
+ Connect to `ws/session?session_id=...`, optionally with `cursor=...` and
193
+ `agent_id=...`. Existing authentication and same-origin checks apply.
194
+
195
+ `hello` retains `snapshot` and `events` and adds `api_version`, `state`, `cursor`,
196
+ and `reset_required`. Subsequent events include a cursor and lightweight state.
197
+ The legacy display snapshot remains on its existing event types.
198
+
199
+ - Without a cursor, hello contains the last 200 cached events.
200
+ - A valid cursor replays events strictly after it, up to the 500-event window.
201
+ - An expired cursor, different stream or future sequence returns
202
+ `reset_required: true`, no replay events and a fresh snapshot/cursor.
203
+ - Invalid cursor syntax produces a WS error and closes the connection.
204
+
205
+ Hello's state is authoritative at its cursor; older events fill the timeline
206
+ and should not overwrite that state. Process live events in cursor order and
207
+ save the last processed cursor for reconnecting. State snapshots, cursor
208
+ allocation, hello subscription and publication share a lock across worker/server
209
+ threads.
210
+
211
+ The stream includes structured Runtime/tool/model events, `runtime_updated`
212
+ notifications for plan, queue, command and resource changes, and `submission`
213
+ notifications containing result records. Agent/background change notifications
214
+ tell clients to re-query the inventory.
215
+
216
+ Events are memory-only and retained by count. Replay guarantees apply inside
217
+ that window; a rollout is not a persistent timing/retry/WS log. Child streams
218
+ are created when first observed and have separate cursors, excluding earlier
219
+ events. Their WS connection is read-only: `ping` works; `send`/`answer` is rejected.
220
+ Observing a child does not enable interactive requests or rollout recording.
221
+
222
+ ## Built-in page extension host
223
+
224
+ An optional `mount(host)` export in `~/.pycodex/frontend/custom.js` is called
225
+ once when the built-in page initializes. Side-effect-only modules still load
226
+ normally. Initialization failures are shown in the page. `?builtin=1` disables
227
+ loading and mounting. A complete HTML replacement owns its initialization.
228
+
229
+ ```js
230
+ export function mount(host) {
231
+ const update = async sessionId => {
232
+ if (!sessionId) return;
233
+ const {state} = await host.request(`api/sessions/${sessionId}/state`);
234
+ if (host.getActiveSessionId() === sessionId) {
235
+ document.body.dataset.agentBusy = String(state.busy);
236
+ }
237
+ };
238
+ update(host.getActiveSessionId()).catch(console.error);
239
+ host.onSessionChanged(id => update(id).catch(console.error));
240
+ }
241
+ ```
242
+
243
+ The frozen host has `apiVersion: 1`:
244
+
245
+ | Method | Contract |
246
+ | --- | --- |
247
+ | `getActiveSessionId()` | Current tab ID; initially may be empty |
248
+ | `selectSession(id)` | Switch to an existing tab; returns a promise |
249
+ | `onSessionChanged(handler)` | Subscribe; returns an unsubscribe function |
250
+ | `getDraft()` | Structured composer draft, including context parts |
251
+ | `setDraft(draft)` | Replace current draft; `draft.parts` must be an array |
252
+ | `openView(href, srcPath)` | Open a file/Git/workspace link for the current session |
253
+ | `request(path, options)` | Fetch workspace-relative JSON; HTTP failures reject |
254
+
255
+ Tab-change callbacks run after the synchronous draft/view switch. Check the
256
+ current session when applying asynchronous results. Host state is browser-local;
257
+ this does not add cross-device draft storage.