pocket-coding 0.4.0__tar.gz → 0.5.0__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 (114) hide show
  1. pocket_coding-0.5.0/PKG-INFO +263 -0
  2. pocket_coding-0.5.0/README.md +238 -0
  3. pocket_coding-0.5.0/pocket_coding.egg-info/PKG-INFO +263 -0
  4. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/pocket_coding.egg-info/SOURCES.txt +32 -0
  5. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/agent/catalog.py +125 -18
  6. pocket_coding-0.5.0/poco/agent/claude_code.py +570 -0
  7. pocket_coding-0.5.0/poco/agent/coco.py +727 -0
  8. pocket_coding-0.5.0/poco/agent/codex_app_server.py +860 -0
  9. pocket_coding-0.5.0/poco/agent/codex_cli.py +274 -0
  10. pocket_coding-0.5.0/poco/agent/common.py +153 -0
  11. pocket_coding-0.5.0/poco/agent/completion_gate.py +63 -0
  12. pocket_coding-0.5.0/poco/agent/cursor_agent.py +488 -0
  13. pocket_coding-0.5.0/poco/agent/factory.py +86 -0
  14. pocket_coding-0.5.0/poco/agent/runner.py +88 -0
  15. pocket_coding-0.5.0/poco/agent/stub.py +160 -0
  16. pocket_coding-0.5.0/poco/agent/tokens.py +63 -0
  17. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/cli.py +38 -8
  18. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/config.py +86 -19
  19. pocket_coding-0.5.0/poco/env_inventory.py +98 -0
  20. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/interaction/card_handlers.py +450 -20
  21. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/interaction/card_models.py +3 -0
  22. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/interaction/service.py +21 -20
  23. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/main.py +225 -4
  24. pocket_coding-0.5.0/poco/platform/common/__init__.py +1 -0
  25. pocket_coding-0.5.0/poco/platform/common/card_renderer.py +17 -0
  26. pocket_coding-0.5.0/poco/platform/common/message_client.py +50 -0
  27. pocket_coding-0.5.0/poco/platform/common/platform.py +8 -0
  28. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/card_gateway.py +1 -1
  29. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/cards.py +457 -108
  30. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/client.py +12 -12
  31. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/debug.py +2 -2
  32. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/gateway.py +44 -12
  33. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/longconn.py +33 -0
  34. pocket_coding-0.5.0/poco/platform/slack/__init__.py +1 -0
  35. pocket_coding-0.5.0/poco/platform/slack/card_gateway.py +241 -0
  36. pocket_coding-0.5.0/poco/platform/slack/cards.py +600 -0
  37. pocket_coding-0.5.0/poco/platform/slack/client.py +220 -0
  38. pocket_coding-0.5.0/poco/platform/slack/debug.py +97 -0
  39. pocket_coding-0.5.0/poco/platform/slack/gateway.py +413 -0
  40. pocket_coding-0.5.0/poco/platform/slack/project_bootstrap.py +234 -0
  41. pocket_coding-0.5.0/poco/platform/slack/socket_mode.py +272 -0
  42. pocket_coding-0.5.0/poco/platform/slack/verification.py +76 -0
  43. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/project/controller.py +8 -2
  44. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/project/models.py +12 -1
  45. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/session/controller.py +21 -1
  46. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/storage/sqlite.py +119 -25
  47. pocket_coding-0.5.0/poco/task/controller.py +630 -0
  48. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/task/dispatcher.py +72 -2
  49. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/task/models.py +37 -3
  50. pocket_coding-0.5.0/poco/task/notifier.py +472 -0
  51. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/pyproject.toml +1 -1
  52. pocket_coding-0.5.0/tests/test_agent_catalog.py +145 -0
  53. pocket_coding-0.5.0/tests/test_agent_runner.py +3192 -0
  54. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_card_gateway.py +743 -5
  55. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_cli.py +78 -0
  56. pocket_coding-0.5.0/tests/test_completion_gate.py +94 -0
  57. pocket_coding-0.5.0/tests/test_config.py +148 -0
  58. pocket_coding-0.5.0/tests/test_debug_api.py +156 -0
  59. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_demo_cards.py +25 -0
  60. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_feishu_client.py +469 -22
  61. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_feishu_gateway.py +123 -14
  62. pocket_coding-0.5.0/tests/test_session_controller.py +98 -0
  63. pocket_coding-0.5.0/tests/test_slack_cards.py +356 -0
  64. pocket_coding-0.5.0/tests/test_slack_client.py +138 -0
  65. pocket_coding-0.5.0/tests/test_slack_endpoints.py +209 -0
  66. pocket_coding-0.5.0/tests/test_slack_gateway.py +316 -0
  67. pocket_coding-0.5.0/tests/test_slack_project_bootstrap.py +272 -0
  68. pocket_coding-0.5.0/tests/test_slack_socket_mode.py +208 -0
  69. pocket_coding-0.5.0/tests/test_slack_verifier.py +85 -0
  70. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_state_persistence.py +38 -1
  71. pocket_coding-0.5.0/tests/test_task_controller.py +753 -0
  72. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_task_dispatcher.py +89 -6
  73. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_task_notifier.py +82 -10
  74. pocket_coding-0.4.0/PKG-INFO +0 -449
  75. pocket_coding-0.4.0/README.md +0 -424
  76. pocket_coding-0.4.0/pocket_coding.egg-info/PKG-INFO +0 -449
  77. pocket_coding-0.4.0/poco/agent/runner.py +0 -1414
  78. pocket_coding-0.4.0/poco/task/controller.py +0 -405
  79. pocket_coding-0.4.0/poco/task/notifier.py +0 -296
  80. pocket_coding-0.4.0/tests/test_agent_catalog.py +0 -64
  81. pocket_coding-0.4.0/tests/test_agent_runner.py +0 -729
  82. pocket_coding-0.4.0/tests/test_config.py +0 -58
  83. pocket_coding-0.4.0/tests/test_debug_api.py +0 -74
  84. pocket_coding-0.4.0/tests/test_session_controller.py +0 -52
  85. pocket_coding-0.4.0/tests/test_task_controller.py +0 -208
  86. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/LICENSE +0 -0
  87. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/pocket_coding.egg-info/dependency_links.txt +0 -0
  88. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/pocket_coding.egg-info/entry_points.txt +0 -0
  89. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/pocket_coding.egg-info/requires.txt +0 -0
  90. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/pocket_coding.egg-info/top_level.txt +0 -0
  91. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/__init__.py +0 -0
  92. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/agent/__init__.py +0 -0
  93. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/demo.py +0 -0
  94. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/interaction/__init__.py +0 -0
  95. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/interaction/card_dispatcher.py +0 -0
  96. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/__init__.py +0 -0
  97. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/__init__.py +0 -0
  98. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/project_bootstrap.py +0 -0
  99. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/platform/feishu/verification.py +0 -0
  100. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/project/__init__.py +0 -0
  101. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/project/bootstrap.py +0 -0
  102. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/session/models.py +0 -0
  103. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/storage/__init__.py +0 -0
  104. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/storage/memory.py +0 -0
  105. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/storage/protocols.py +0 -0
  106. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/task/__init__.py +0 -0
  107. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/task/rendering.py +0 -0
  108. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/workspace/controller.py +0 -0
  109. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/poco/workspace/models.py +0 -0
  110. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/setup.cfg +0 -0
  111. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_card_dispatcher.py +0 -0
  112. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_demo_api.py +0 -0
  113. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_feishu_longconn.py +0 -0
  114. {pocket_coding-0.4.0 → pocket_coding-0.5.0}/tests/test_health.py +0 -0
@@ -0,0 +1,263 @@
1
+ Metadata-Version: 2.4
2
+ Name: pocket-coding
3
+ Version: 0.5.0
4
+ Summary: PoCo: pocket coding for server-side AI agents through mobile messaging entrypoints.
5
+ Author: Yihanc
6
+ Keywords: ai-agent,automation,developer-tools,feishu,mobile
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: fastapi<1.0,>=0.115
19
+ Requires-Dist: lark-oapi<2.0,>=1.5
20
+ Requires-Dist: uvicorn<1.0,>=0.32
21
+ Provides-Extra: dev
22
+ Requires-Dist: build<2.0,>=1.2; extra == "dev"
23
+ Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # PoCo
27
+
28
+ PoCo is a Python-first scaffold for driving server-side coding agents from chat surfaces (Feishu today, Slack since this release). Projects, workspaces, sessions, and tasks all live in PoCo; the chat bot is a thin interactive front-end for them.
29
+
30
+ ## Supported Platforms
31
+
32
+ - **Feishu** — DM control plane + per-project group chat, over webhook **or** long connection (`longconn`, default).
33
+ - **Slack** — DM control plane + per-project channel, over Socket Mode (default) **or** HTTP webhooks. Slash command `/poco` opens the same project-list card the DM surface renders.
34
+
35
+ Both platforms run simultaneously when configured. Every `Task` / `Project` remembers which platform it was created from, and replies are routed back to the originating surface by `PlatformRoutingTaskNotifier`.
36
+
37
+ ## Supported Agent Backends
38
+
39
+ - `codex` (default) — runs through `codex app-server` over stdio; streams real `agentMessage/delta` events into task cards.
40
+ - `claude_code`
41
+ - `cursor_agent`
42
+ - `coco` (Trae CLI)
43
+ - `stub` — local flow validation only.
44
+
45
+ Backend per project is picked from the DM project-creation card. Environment variables are server-side defaults only.
46
+
47
+ ## Quick Start
48
+
49
+ ```bash
50
+ python3 -m pip install -e .
51
+ poco config # interactive prompt for Feishu credentials
52
+ poco start
53
+ poco status
54
+ ```
55
+
56
+ `poco config` writes `~/.poco/poco.config.json`. Environment variables override file values at runtime. To run with no chat platform at all (just the HTTP demo surface and agent runner), skip `poco config` and start directly:
57
+
58
+ ```bash
59
+ poco start
60
+ curl http://127.0.0.1:8000/health
61
+ ```
62
+
63
+ `/health` explicitly lists what is missing for each platform.
64
+
65
+ ## Configuration
66
+
67
+ PoCo reads config from three layers, in decreasing precedence:
68
+
69
+ 1. `POCO_*` environment variables
70
+ 2. Flat keys in `~/.poco/poco.config.json` (same name as the env var)
71
+ 3. Sectioned keys in the same file, e.g. `{"feishu": {"app_id": "..."}, "slack": {"bot_token": "..."}}`
72
+
73
+ ### Feishu
74
+
75
+ | Key | Purpose |
76
+ | --- | --- |
77
+ | `POCO_FEISHU_APP_ID` / `POCO_FEISHU_APP_SECRET` | Required to enable the Feishu integration. |
78
+ | `POCO_FEISHU_DELIVERY_MODE` | `longconn` (default) or `webhook`. |
79
+ | `POCO_FEISHU_VERIFICATION_TOKEN` | Optional webhook token. Lowers friction when unset, lowers security too. |
80
+ | `POCO_FEISHU_ENCRYPT_KEY` | Enables signature validation on webhook callbacks. Encrypted payload bodies are not supported yet. |
81
+ | `POCO_FEISHU_API_BASE_URL` | Defaults to `https://open.feishu.cn`. |
82
+
83
+ Long-connection mode authenticates inbound events via the long-connection session itself and routes both message events and card callbacks over the same listener. Callback token/signature settings only apply to webhook delivery.
84
+
85
+ ### Slack
86
+
87
+ | Key | Purpose |
88
+ | --- | --- |
89
+ | `POCO_SLACK_BOT_TOKEN` | `xoxb-…` bot token. Required. |
90
+ | `POCO_SLACK_SIGNING_SECRET` | Required for HTTP webhook signature verification. |
91
+ | `POCO_SLACK_APP_TOKEN` | `xapp-…` app-level token. Required when `POCO_SLACK_DELIVERY_MODE=socket`. |
92
+ | `POCO_SLACK_DELIVERY_MODE` | `socket` (default) or `webhook`. |
93
+
94
+ Slack is considered enabled when bot token + signing secret (+ app token, for socket mode) are all set. `/poco` slash command posts the project-list card as an ephemeral reply.
95
+
96
+ ### Agent backend (server-side)
97
+
98
+ Minimum:
99
+
100
+ ```bash
101
+ export POCO_AGENT_BACKEND="codex"
102
+ export POCO_CODEX_COMMAND="codex"
103
+ export POCO_CODEX_WORKDIR="/absolute/path/to/your/repo"
104
+ ```
105
+
106
+ Optional per-backend tuning:
107
+
108
+ ```bash
109
+ # Codex
110
+ export POCO_CODEX_MODEL="gpt-5"
111
+ export POCO_CODEX_SANDBOX="workspace-write"
112
+ export POCO_CODEX_APPROVAL_POLICY="never"
113
+ export POCO_CODEX_TIMEOUT_SECONDS="900"
114
+ export POCO_CODEX_TRANSPORT_IDLE_SECONDS="1800"
115
+
116
+ # Claude Code
117
+ export POCO_CLAUDE_COMMAND="claude"
118
+ export POCO_CLAUDE_WORKDIR="/absolute/path/to/your/repo"
119
+ export POCO_CLAUDE_MODEL="sonnet"
120
+ export POCO_CLAUDE_PERMISSION_MODE="default"
121
+ export POCO_CLAUDE_TIMEOUT_SECONDS="900"
122
+
123
+ # Cursor Agent
124
+ export POCO_CURSOR_COMMAND="cursor-agent"
125
+ export POCO_CURSOR_WORKDIR="/absolute/path/to/your/repo"
126
+ export POCO_CURSOR_MODEL="auto"
127
+ export POCO_CURSOR_MODE="default"
128
+ export POCO_CURSOR_SANDBOX="default"
129
+ export POCO_CURSOR_TIMEOUT_SECONDS="900"
130
+
131
+ # Trae CLI (coco)
132
+ export POCO_COCO_COMMAND="traecli"
133
+ export POCO_COCO_WORKDIR="/absolute/path/to/your/repo"
134
+ export POCO_COCO_MODEL="GPT-5"
135
+ export POCO_COCO_APPROVAL_MODE="default"
136
+ export POCO_COCO_TIMEOUT_SECONDS="900"
137
+ ```
138
+
139
+ ### State backend
140
+
141
+ ```bash
142
+ export POCO_STATE_BACKEND="sqlite" # default
143
+ export POCO_STATE_DB_PATH="~/.poco/poco.db"
144
+ ```
145
+
146
+ SQLite is the default runtime path. It persists projects, workspace context, sessions, and tasks so a restart does not lose group/workspace bookkeeping.
147
+
148
+ ## Interaction Model
149
+
150
+ - **DM**: control plane. `New` creates a project and its bound group; `Manage` lists projects and exposes delete. DM inbound messages always render the project-list card.
151
+ - **Group**: workspace card with `Stop` / `Working Dir` / `Agent` actions, plus plain-text task submission. `Working Dir` selection (folder browse, manual entry, recent directories, and project-level presets) stays inside cards. `Agent` opens a dedicated selection card.
152
+
153
+ Group behavior:
154
+
155
+ - Plain text in a bound group is a task prompt.
156
+ - Tasks in a project run in a single-project queue — a new message is queued while another task is still active.
157
+ - Codex groups persist the upstream thread id and resume it via `codex app-server`, so follow-up messages continue the same Codex conversation.
158
+ - Task cards show bracketed status in the title (e.g. `[Running] Task: … (codex, /srv/api)`), and live-stream throttled agent output.
159
+ - Waiting task cards expose `Approve` / `Reject`; later state transitions update the same card in place before falling back to a new message.
160
+ - Workspace cards are refreshed when the latest task changes and keep a bound `message_id` across updates.
161
+
162
+ ## HTTP Surface
163
+
164
+ Core endpoints:
165
+
166
+ | Path | Purpose |
167
+ | --- | --- |
168
+ | `GET /health` | Runtime readiness + missing/warn summary for both platforms. |
169
+ | `GET /tasks`, `GET /tasks/{task_id}` | Raw task state. |
170
+ | `GET /debug/feishu`, `GET /debug/slack` | Recent inbound events, outbound attempts, errors, listener snapshot for the respective platform. |
171
+ | `GET /debug/env` | Presence/length of whitelisted env keys (no values are returned). |
172
+
173
+ Feishu endpoints:
174
+
175
+ - `POST /platform/feishu/events`
176
+ - `POST /platform/feishu/card-actions`
177
+
178
+ Slack endpoints:
179
+
180
+ - `POST /platform/slack/events` (JSON body, `X-Slack-Signature` verified)
181
+ - `POST /platform/slack/interactive` (form-encoded `payload=…`)
182
+ - `POST /platform/slack/commands` (form-encoded slash command)
183
+
184
+ Demo endpoints (platform-agnostic):
185
+
186
+ - `POST /demo/command` — `{"text":"/run …"}`
187
+ - `POST /demo/tasks/{task_id}/approve`
188
+ - `POST /demo/tasks/{task_id}/reject`
189
+ - `GET /demo/cards/dm/projects`
190
+ - `POST /demo/card-actions`
191
+
192
+ ### Supported text commands
193
+
194
+ In a bound project group, any plain text is treated as a task prompt. The explicit commands are still accepted:
195
+
196
+ - `/run <prompt>` — start a task.
197
+ - `/status <task_id>`
198
+ - `/approve <task_id>` / `/reject <task_id>`
199
+ - `/help`
200
+
201
+ If the prompt starts with `confirm:`, the stub runner and the Codex backend pause at a confirmation checkpoint; `/approve <task_id>` resumes.
202
+
203
+ ## Local Demo Example
204
+
205
+ Without any chat platform configured:
206
+
207
+ ```bash
208
+ curl -X POST http://127.0.0.1:8000/demo/command \
209
+ -H 'Content-Type: application/json' \
210
+ -d '{"text":"/run Reply with exactly: DEMO_OK"}'
211
+
212
+ curl http://127.0.0.1:8000/tasks/<task_id>
213
+ ```
214
+
215
+ Approval flow:
216
+
217
+ ```bash
218
+ curl -X POST http://127.0.0.1:8000/demo/command \
219
+ -H 'Content-Type: application/json' \
220
+ -d '{"text":"/run confirm: Reply with exactly: APPROVED"}'
221
+
222
+ curl -X POST http://127.0.0.1:8000/demo/tasks/<task_id>/approve
223
+ ```
224
+
225
+ ## CLI
226
+
227
+ ```bash
228
+ poco config # interactive Feishu credential prompt
229
+ poco start # starts uvicorn in the background, pid in ~/.poco/poco.pid
230
+ poco status # pid + /health summary
231
+ poco restart
232
+ poco shutdown
233
+ ```
234
+
235
+ Manual fallback:
236
+
237
+ ```bash
238
+ uvicorn poco.main:app --reload
239
+ ```
240
+
241
+ ## Packaging
242
+
243
+ ```bash
244
+ python3 -m pip install build
245
+ python3 -m build
246
+ # produces dist/*.whl and dist/*.tar.gz
247
+ ```
248
+
249
+ When published, installation will be:
250
+
251
+ ```bash
252
+ python3 -m pip install pocket-coding
253
+ ```
254
+
255
+ ## Debugging Reply Issues
256
+
257
+ When a chat message does not get a reply, compare `/debug/feishu` or `/debug/slack` against the expected flow:
258
+
259
+ - If there are no recent inbound entries: the platform never reached PoCo (check long-connection/socket-mode listener status under `listener`, or the public webhook route).
260
+ - If inbound entries exist but there are no outbound attempts: PoCo dropped the message (e.g. unbound group, ignored bot message).
261
+ - If outbound attempts exist but errors accumulate: the platform rejected the send (scope/permission issues usually).
262
+
263
+ `/health` also surfaces listener readiness for both platforms plus the current agent backend readiness.
@@ -0,0 +1,238 @@
1
+ # PoCo
2
+
3
+ PoCo is a Python-first scaffold for driving server-side coding agents from chat surfaces (Feishu today, Slack since this release). Projects, workspaces, sessions, and tasks all live in PoCo; the chat bot is a thin interactive front-end for them.
4
+
5
+ ## Supported Platforms
6
+
7
+ - **Feishu** — DM control plane + per-project group chat, over webhook **or** long connection (`longconn`, default).
8
+ - **Slack** — DM control plane + per-project channel, over Socket Mode (default) **or** HTTP webhooks. Slash command `/poco` opens the same project-list card the DM surface renders.
9
+
10
+ Both platforms run simultaneously when configured. Every `Task` / `Project` remembers which platform it was created from, and replies are routed back to the originating surface by `PlatformRoutingTaskNotifier`.
11
+
12
+ ## Supported Agent Backends
13
+
14
+ - `codex` (default) — runs through `codex app-server` over stdio; streams real `agentMessage/delta` events into task cards.
15
+ - `claude_code`
16
+ - `cursor_agent`
17
+ - `coco` (Trae CLI)
18
+ - `stub` — local flow validation only.
19
+
20
+ Backend per project is picked from the DM project-creation card. Environment variables are server-side defaults only.
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ python3 -m pip install -e .
26
+ poco config # interactive prompt for Feishu credentials
27
+ poco start
28
+ poco status
29
+ ```
30
+
31
+ `poco config` writes `~/.poco/poco.config.json`. Environment variables override file values at runtime. To run with no chat platform at all (just the HTTP demo surface and agent runner), skip `poco config` and start directly:
32
+
33
+ ```bash
34
+ poco start
35
+ curl http://127.0.0.1:8000/health
36
+ ```
37
+
38
+ `/health` explicitly lists what is missing for each platform.
39
+
40
+ ## Configuration
41
+
42
+ PoCo reads config from three layers, in decreasing precedence:
43
+
44
+ 1. `POCO_*` environment variables
45
+ 2. Flat keys in `~/.poco/poco.config.json` (same name as the env var)
46
+ 3. Sectioned keys in the same file, e.g. `{"feishu": {"app_id": "..."}, "slack": {"bot_token": "..."}}`
47
+
48
+ ### Feishu
49
+
50
+ | Key | Purpose |
51
+ | --- | --- |
52
+ | `POCO_FEISHU_APP_ID` / `POCO_FEISHU_APP_SECRET` | Required to enable the Feishu integration. |
53
+ | `POCO_FEISHU_DELIVERY_MODE` | `longconn` (default) or `webhook`. |
54
+ | `POCO_FEISHU_VERIFICATION_TOKEN` | Optional webhook token. Lowers friction when unset, lowers security too. |
55
+ | `POCO_FEISHU_ENCRYPT_KEY` | Enables signature validation on webhook callbacks. Encrypted payload bodies are not supported yet. |
56
+ | `POCO_FEISHU_API_BASE_URL` | Defaults to `https://open.feishu.cn`. |
57
+
58
+ Long-connection mode authenticates inbound events via the long-connection session itself and routes both message events and card callbacks over the same listener. Callback token/signature settings only apply to webhook delivery.
59
+
60
+ ### Slack
61
+
62
+ | Key | Purpose |
63
+ | --- | --- |
64
+ | `POCO_SLACK_BOT_TOKEN` | `xoxb-…` bot token. Required. |
65
+ | `POCO_SLACK_SIGNING_SECRET` | Required for HTTP webhook signature verification. |
66
+ | `POCO_SLACK_APP_TOKEN` | `xapp-…` app-level token. Required when `POCO_SLACK_DELIVERY_MODE=socket`. |
67
+ | `POCO_SLACK_DELIVERY_MODE` | `socket` (default) or `webhook`. |
68
+
69
+ Slack is considered enabled when bot token + signing secret (+ app token, for socket mode) are all set. `/poco` slash command posts the project-list card as an ephemeral reply.
70
+
71
+ ### Agent backend (server-side)
72
+
73
+ Minimum:
74
+
75
+ ```bash
76
+ export POCO_AGENT_BACKEND="codex"
77
+ export POCO_CODEX_COMMAND="codex"
78
+ export POCO_CODEX_WORKDIR="/absolute/path/to/your/repo"
79
+ ```
80
+
81
+ Optional per-backend tuning:
82
+
83
+ ```bash
84
+ # Codex
85
+ export POCO_CODEX_MODEL="gpt-5"
86
+ export POCO_CODEX_SANDBOX="workspace-write"
87
+ export POCO_CODEX_APPROVAL_POLICY="never"
88
+ export POCO_CODEX_TIMEOUT_SECONDS="900"
89
+ export POCO_CODEX_TRANSPORT_IDLE_SECONDS="1800"
90
+
91
+ # Claude Code
92
+ export POCO_CLAUDE_COMMAND="claude"
93
+ export POCO_CLAUDE_WORKDIR="/absolute/path/to/your/repo"
94
+ export POCO_CLAUDE_MODEL="sonnet"
95
+ export POCO_CLAUDE_PERMISSION_MODE="default"
96
+ export POCO_CLAUDE_TIMEOUT_SECONDS="900"
97
+
98
+ # Cursor Agent
99
+ export POCO_CURSOR_COMMAND="cursor-agent"
100
+ export POCO_CURSOR_WORKDIR="/absolute/path/to/your/repo"
101
+ export POCO_CURSOR_MODEL="auto"
102
+ export POCO_CURSOR_MODE="default"
103
+ export POCO_CURSOR_SANDBOX="default"
104
+ export POCO_CURSOR_TIMEOUT_SECONDS="900"
105
+
106
+ # Trae CLI (coco)
107
+ export POCO_COCO_COMMAND="traecli"
108
+ export POCO_COCO_WORKDIR="/absolute/path/to/your/repo"
109
+ export POCO_COCO_MODEL="GPT-5"
110
+ export POCO_COCO_APPROVAL_MODE="default"
111
+ export POCO_COCO_TIMEOUT_SECONDS="900"
112
+ ```
113
+
114
+ ### State backend
115
+
116
+ ```bash
117
+ export POCO_STATE_BACKEND="sqlite" # default
118
+ export POCO_STATE_DB_PATH="~/.poco/poco.db"
119
+ ```
120
+
121
+ SQLite is the default runtime path. It persists projects, workspace context, sessions, and tasks so a restart does not lose group/workspace bookkeeping.
122
+
123
+ ## Interaction Model
124
+
125
+ - **DM**: control plane. `New` creates a project and its bound group; `Manage` lists projects and exposes delete. DM inbound messages always render the project-list card.
126
+ - **Group**: workspace card with `Stop` / `Working Dir` / `Agent` actions, plus plain-text task submission. `Working Dir` selection (folder browse, manual entry, recent directories, and project-level presets) stays inside cards. `Agent` opens a dedicated selection card.
127
+
128
+ Group behavior:
129
+
130
+ - Plain text in a bound group is a task prompt.
131
+ - Tasks in a project run in a single-project queue — a new message is queued while another task is still active.
132
+ - Codex groups persist the upstream thread id and resume it via `codex app-server`, so follow-up messages continue the same Codex conversation.
133
+ - Task cards show bracketed status in the title (e.g. `[Running] Task: … (codex, /srv/api)`), and live-stream throttled agent output.
134
+ - Waiting task cards expose `Approve` / `Reject`; later state transitions update the same card in place before falling back to a new message.
135
+ - Workspace cards are refreshed when the latest task changes and keep a bound `message_id` across updates.
136
+
137
+ ## HTTP Surface
138
+
139
+ Core endpoints:
140
+
141
+ | Path | Purpose |
142
+ | --- | --- |
143
+ | `GET /health` | Runtime readiness + missing/warn summary for both platforms. |
144
+ | `GET /tasks`, `GET /tasks/{task_id}` | Raw task state. |
145
+ | `GET /debug/feishu`, `GET /debug/slack` | Recent inbound events, outbound attempts, errors, listener snapshot for the respective platform. |
146
+ | `GET /debug/env` | Presence/length of whitelisted env keys (no values are returned). |
147
+
148
+ Feishu endpoints:
149
+
150
+ - `POST /platform/feishu/events`
151
+ - `POST /platform/feishu/card-actions`
152
+
153
+ Slack endpoints:
154
+
155
+ - `POST /platform/slack/events` (JSON body, `X-Slack-Signature` verified)
156
+ - `POST /platform/slack/interactive` (form-encoded `payload=…`)
157
+ - `POST /platform/slack/commands` (form-encoded slash command)
158
+
159
+ Demo endpoints (platform-agnostic):
160
+
161
+ - `POST /demo/command` — `{"text":"/run …"}`
162
+ - `POST /demo/tasks/{task_id}/approve`
163
+ - `POST /demo/tasks/{task_id}/reject`
164
+ - `GET /demo/cards/dm/projects`
165
+ - `POST /demo/card-actions`
166
+
167
+ ### Supported text commands
168
+
169
+ In a bound project group, any plain text is treated as a task prompt. The explicit commands are still accepted:
170
+
171
+ - `/run <prompt>` — start a task.
172
+ - `/status <task_id>`
173
+ - `/approve <task_id>` / `/reject <task_id>`
174
+ - `/help`
175
+
176
+ If the prompt starts with `confirm:`, the stub runner and the Codex backend pause at a confirmation checkpoint; `/approve <task_id>` resumes.
177
+
178
+ ## Local Demo Example
179
+
180
+ Without any chat platform configured:
181
+
182
+ ```bash
183
+ curl -X POST http://127.0.0.1:8000/demo/command \
184
+ -H 'Content-Type: application/json' \
185
+ -d '{"text":"/run Reply with exactly: DEMO_OK"}'
186
+
187
+ curl http://127.0.0.1:8000/tasks/<task_id>
188
+ ```
189
+
190
+ Approval flow:
191
+
192
+ ```bash
193
+ curl -X POST http://127.0.0.1:8000/demo/command \
194
+ -H 'Content-Type: application/json' \
195
+ -d '{"text":"/run confirm: Reply with exactly: APPROVED"}'
196
+
197
+ curl -X POST http://127.0.0.1:8000/demo/tasks/<task_id>/approve
198
+ ```
199
+
200
+ ## CLI
201
+
202
+ ```bash
203
+ poco config # interactive Feishu credential prompt
204
+ poco start # starts uvicorn in the background, pid in ~/.poco/poco.pid
205
+ poco status # pid + /health summary
206
+ poco restart
207
+ poco shutdown
208
+ ```
209
+
210
+ Manual fallback:
211
+
212
+ ```bash
213
+ uvicorn poco.main:app --reload
214
+ ```
215
+
216
+ ## Packaging
217
+
218
+ ```bash
219
+ python3 -m pip install build
220
+ python3 -m build
221
+ # produces dist/*.whl and dist/*.tar.gz
222
+ ```
223
+
224
+ When published, installation will be:
225
+
226
+ ```bash
227
+ python3 -m pip install pocket-coding
228
+ ```
229
+
230
+ ## Debugging Reply Issues
231
+
232
+ When a chat message does not get a reply, compare `/debug/feishu` or `/debug/slack` against the expected flow:
233
+
234
+ - If there are no recent inbound entries: the platform never reached PoCo (check long-connection/socket-mode listener status under `listener`, or the public webhook route).
235
+ - If inbound entries exist but there are no outbound attempts: PoCo dropped the message (e.g. unbound group, ignored bot message).
236
+ - If outbound attempts exist but errors accumulate: the platform rejected the send (scope/permission issues usually).
237
+
238
+ `/health` also surfaces listener readiness for both platforms plus the current agent backend readiness.