teamshare-bridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -0
- package/dist/bridge/daemon.d.ts +1 -0
- package/dist/bridge/daemon.js +230 -0
- package/dist/bridge/daemon.js.map +1 -0
- package/dist/bridge/index.d.ts +2 -0
- package/dist/bridge/index.js +273 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/local-server.d.ts +29 -0
- package/dist/bridge/local-server.js +87 -0
- package/dist/bridge/local-server.js.map +1 -0
- package/dist/bridge/protocol.d.ts +4 -0
- package/dist/bridge/protocol.js +151 -0
- package/dist/bridge/protocol.js.map +1 -0
- package/dist/bridge/service.d.ts +32 -0
- package/dist/bridge/service.js +248 -0
- package/dist/bridge/service.js.map +1 -0
- package/dist/bridge/spawn.d.ts +15 -0
- package/dist/bridge/spawn.js +271 -0
- package/dist/bridge/spawn.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +543 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/lib/api.d.ts +191 -0
- package/dist/lib/api.js +209 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/brief.d.ts +16 -0
- package/dist/lib/brief.js +115 -0
- package/dist/lib/brief.js.map +1 -0
- package/dist/lib/chat-reply.d.ts +29 -0
- package/dist/lib/chat-reply.js +126 -0
- package/dist/lib/chat-reply.js.map +1 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +81 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/doc-reply.d.ts +22 -0
- package/dist/lib/doc-reply.js +108 -0
- package/dist/lib/doc-reply.js.map +1 -0
- package/dist/lib/llm.d.ts +50 -0
- package/dist/lib/llm.js +444 -0
- package/dist/lib/llm.js.map +1 -0
- package/dist/lib/lock.d.ts +41 -0
- package/dist/lib/lock.js +148 -0
- package/dist/lib/lock.js.map +1 -0
- package/dist/lib/models.d.ts +30 -0
- package/dist/lib/models.js +51 -0
- package/dist/lib/models.js.map +1 -0
- package/dist/lib/partial-stream.d.ts +25 -0
- package/dist/lib/partial-stream.js +99 -0
- package/dist/lib/partial-stream.js.map +1 -0
- package/dist/lib/session-stream.d.ts +38 -0
- package/dist/lib/session-stream.js +191 -0
- package/dist/lib/session-stream.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# teamshare-bridge
|
|
2
|
+
|
|
3
|
+
The local bridge that lets AI agents work TeamShare tasks from your machine.
|
|
4
|
+
|
|
5
|
+
TeamShare is the **coordination hub**: agents are registered in the app
|
|
6
|
+
(identity, capabilities, task assignments, run history). The AI itself runs
|
|
7
|
+
here — on your computer — connected to TeamShare through three methods, all
|
|
8
|
+
provided by this package:
|
|
9
|
+
|
|
10
|
+
| Method | Binary | Trigger | Requirements |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| **Terminal session** | `teamshare-agent` | You paste a command (or click "Copy run command" in the app) | Node 18+, no daemon |
|
|
13
|
+
| **Auto-wake daemon** | `teamshare-bridge start` | A task is assigned to your agent (or it is @mentioned) → the daemon spawns a session automatically | The daemon must be running (`autostart on` keeps it alive across reboots) |
|
|
14
|
+
| **Run button (deep link)** | `teamshare-bridge register` + `serve` | The TeamShare website's "Run agent" button hands off via `teamshare://` | One-time registration; OS cold-launches the bridge |
|
|
15
|
+
|
|
16
|
+
All three share the same core: fetch a task brief (task, comments, project
|
|
17
|
+
files), drive a working session (opencode / Claude / built-in headless LLM
|
|
18
|
+
loop), and report back through the TeamShare API.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
Requires **Node.js 18+**. One shot (Windows):
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
irm https://raw.githubusercontent.com/dominionbanjo/teamshare-bridge/main/scripts/install.ps1 | iex
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
One shot (macOS/Linux):
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
curl -fsSL https://raw.githubusercontent.com/dominionbanjo/teamshare-bridge/main/scripts/install.sh | bash
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or install directly from npm (global):
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
npm i -g teamshare-bridge # published package
|
|
40
|
+
npm i -g . # from this repo
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You now have two commands:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
teamshare-agent # CLI session runner (methods 1)
|
|
47
|
+
teamshare-bridge # daemon + protocol handler (methods 2 and 3)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Run
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
teamshare-bridge start # daemon in the background (pid file + ~/.teamshare/daemon.log)
|
|
54
|
+
teamshare-bridge stop # stop it
|
|
55
|
+
teamshare-bridge restart # stop + start
|
|
56
|
+
teamshare-bridge status # pid/log/autostart + per-agent busy/idle
|
|
57
|
+
teamshare-bridge daemon # foreground (debugging)
|
|
58
|
+
teamshare-bridge autostart on # start at login: Task Scheduler (admin) or Startup-folder launcher (no admin)
|
|
59
|
+
teamshare-bridge autostart off # remove it
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick start
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
# 1. Save your agent's key (from the app: Agents → your agent → Launch Console)
|
|
66
|
+
teamshare-agent connect --agent <agent-id> --key ts_...
|
|
67
|
+
|
|
68
|
+
# 2a. Work one task explicitly
|
|
69
|
+
teamshare-agent run --task <task-id> --agent <agent-id> --key ts_...
|
|
70
|
+
|
|
71
|
+
# 2b. Or let the agent pick up its next assigned task (priority first, then oldest)
|
|
72
|
+
teamshare-agent run --agent <agent-id> --key ts_...
|
|
73
|
+
|
|
74
|
+
# 2c. Auto-wake: keep the daemon running, sessions spawn on assignment
|
|
75
|
+
teamshare-bridge start
|
|
76
|
+
teamshare-bridge autostart on # ...and it comes back after a reboot
|
|
77
|
+
|
|
78
|
+
# 3. Run button on the website (registers the teamshare:// scheme)
|
|
79
|
+
teamshare-bridge register
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Headless mode (no harness, no opencode/Claude — just an LLM API key)
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
LLM_BASE_URL=https://openrouter.ai/api/v1 \
|
|
86
|
+
LLM_API_KEY=sk-... \
|
|
87
|
+
LLM_MODEL=deepseek/deepseek-chat \
|
|
88
|
+
teamshare-agent run --task <task-id> --agent <agent-id> --key ts_... --self
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The headless loop is an OpenAI-compatible **function-calling** loop: the model
|
|
92
|
+
gets the task brief plus tools (`get_task`, `list_tasks`, `create_task`,
|
|
93
|
+
`update_task`, `add_comment`, `list_documents`, `read_document`, `search`,
|
|
94
|
+
`ask_human`, `wait_for_answer`, `list_subtasks`, `create_subtask`,
|
|
95
|
+
`update_subtask`). It is instructed to break the task into 3-8 real
|
|
96
|
+
subtasks (`create_subtask`) and check each off (`update_subtask done: true`)
|
|
97
|
+
as work progresses — the human sees the live breakdown in the app. It posts
|
|
98
|
+
milestone comments and finishes by moving the task to `in_review` /
|
|
99
|
+
`resolved`. Any OpenAI-compatible endpoint works (OpenAI, DeepSeek, OpenRouter,
|
|
100
|
+
Together, local vLLM/Ollama…).
|
|
101
|
+
|
|
102
|
+
**One session per agent:** `run`/`chat`/`doc` acquire a per-agent lock
|
|
103
|
+
(`~/.teamshare/locks/<agentId>.lock`, auto-released on exit, stale locks
|
|
104
|
+
reclaimed) and wait for a busy agent instead of starting a second session.
|
|
105
|
+
The daemon queues wakes for a busy agent and spawns them when it frees up;
|
|
106
|
+
`teamshare-bridge status` shows `BUSY` per agent.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## How a session works
|
|
111
|
+
|
|
112
|
+
1. **Heartbeat** — the agent's status flips to `working` in the app.
|
|
113
|
+
2. **Brief** — the CLI fetches the task (description, comment thread, project
|
|
114
|
+
name) and downloads project files (text files truncated to ~20k chars;
|
|
115
|
+
links return their URL). The brief is written to
|
|
116
|
+
`<temp>/teamshare-task-<taskId>.md` and printed as a preview.
|
|
117
|
+
3. **Work** — either a harness is spawned (`opencode run "…"`, `claude -p "…"`,
|
|
118
|
+
auto-detected in that order) or the `--self` LLM loop runs.
|
|
119
|
+
4. **Report** — a closing summary comment is posted (skip with `--no-report`).
|
|
120
|
+
5. **Watch** — the CLI polls the task status every 15s (up to 20 min) until it
|
|
121
|
+
leaves `open`/`in_progress`, then heartbeats back to `online`.
|
|
122
|
+
|
|
123
|
+
## Command reference
|
|
124
|
+
|
|
125
|
+
### `teamshare-agent`
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
teamshare-agent connect --agent <id> --key <ts_...>
|
|
129
|
+
Test the connection and save the agent key in ~/.teamshare/config.json.
|
|
130
|
+
|
|
131
|
+
teamshare-agent run --task <taskId> --agent <id> --key <ts_...> [options]
|
|
132
|
+
Work a specific task. Without --task, picks the agent's next assigned task
|
|
133
|
+
from its inbox (urgent > high > medium > low, then oldest; skips
|
|
134
|
+
in_review/resolved/closed).
|
|
135
|
+
|
|
136
|
+
teamshare-agent chat --project <projectId> --agent <id> --key <ts_...> [--mention <token>]
|
|
137
|
+
Reply to every project-chat message that @mentions the agent. Replies
|
|
138
|
+
stream token-by-token into the channel when the provider supports it
|
|
139
|
+
(Phase C); the final message is still posted via the API.
|
|
140
|
+
|
|
141
|
+
teamshare-agent doc --document <documentId> --agent <id> --key <ts_...> [--mention <token>]
|
|
142
|
+
Document assistant (Phase F): read the document (Word/PPT/Excel/PDF text
|
|
143
|
+
via GET /documents/:id/extract) and reply to every comment that @mentions
|
|
144
|
+
the agent in its thread.
|
|
145
|
+
|
|
146
|
+
teamshare-agent continue --task <taskId> --agent <id> --key <ts_...> [--self]
|
|
147
|
+
Resume a previous --self session for a task (Phase E continuation).
|
|
148
|
+
|
|
149
|
+
--harness auto|opencode|claude|none session driver (default: config or auto)
|
|
150
|
+
--self headless LLM loop (needs LLM_API_KEY)
|
|
151
|
+
--no-report skip the closing summary comment
|
|
152
|
+
--session <id> live-console session id (default: auto)
|
|
153
|
+
--model <provider:model> model override (catalog id, e.g. gemini:gemini-3.1-flash-lite)
|
|
154
|
+
--temperature <0-2> sampling temperature override
|
|
155
|
+
--max-tokens <n> token budget override
|
|
156
|
+
--system-prompt <text> | --system-prompt-file <path>
|
|
157
|
+
--ask-timeout <sec> ask_human wait limit (env TEAMSHARE_ASK_TIMEOUT)
|
|
158
|
+
--continue resume a previous --self session
|
|
159
|
+
--api-url <url> TeamShare API base override
|
|
160
|
+
|
|
161
|
+
teamshare-agent --help
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Model precedence: **CLI flag > agent settings (set in the app, delivered via
|
|
165
|
+
deep link or auto-wake) > env (`LLM_MODEL`/`LLM_TEMPERATURE`/`LLM_MAX_TOKENS`)
|
|
166
|
+
> defaults**. Model ids use the curated `<provider>:<model>` catalog
|
|
167
|
+
(`docs/agent-models.md`) — the provider's OpenAI-compatible base URL is
|
|
168
|
+
resolved automatically; `LLM_BASE_URL` always wins when set.
|
|
169
|
+
|
|
170
|
+
### `teamshare-bridge`
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
teamshare-bridge daemon [--port N]
|
|
174
|
+
Auto-wake listener: keeps a WebSocket to TeamShare per configured agent
|
|
175
|
+
and spawns a session on wake events (task assigned, @mention in a comment
|
|
176
|
+
or chat). Also serves the localhost probe the website detects.
|
|
177
|
+
|
|
178
|
+
teamshare-bridge register
|
|
179
|
+
Registers the teamshare:// URI scheme (per-user; Windows registry /
|
|
180
|
+
macOS app bundle / Linux .desktop). No admin rights needed.
|
|
181
|
+
|
|
182
|
+
teamshare-bridge serve "<teamshare://agent/run?runToken=...&task=...>"
|
|
183
|
+
OS-invoked: redeems the run token and spawns the session.
|
|
184
|
+
|
|
185
|
+
teamshare-bridge add-agent --agent <id> --key <ts_...>
|
|
186
|
+
Saves an agent key without testing the connection.
|
|
187
|
+
|
|
188
|
+
teamshare-bridge status [--port N]
|
|
189
|
+
Shows config path, port, per-agent BUSY/idle (stale locks flagged),
|
|
190
|
+
harness, quiet hours, scheme.
|
|
191
|
+
|
|
192
|
+
teamshare-bridge clear-lock --agent <id>
|
|
193
|
+
Force-removes a stale session lock. Crashed sessions (dead PID) and
|
|
194
|
+
locks older than 8h are auto-reclaimed; this is the manual override.
|
|
195
|
+
|
|
196
|
+
teamshare-bridge probe [--port N]
|
|
197
|
+
Starts just the localhost probe server (for testing).
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Configuration
|
|
201
|
+
|
|
202
|
+
`~/.teamshare/config.json` — created on first run with defaults:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"agents": [{ "agentId": "...", "apiKey": "ts_..." }],
|
|
207
|
+
"port": 48242,
|
|
208
|
+
"harness": "auto",
|
|
209
|
+
"quietHours": null,
|
|
210
|
+
"wakeRules": null
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
| Key | Meaning |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `agents` | Agent id + API key pairs the daemon listens for (`teamshare-agent connect` / `teamshare-bridge add-agent` populate this) |
|
|
217
|
+
| `port` | Localhost probe port. Picked at **random** from `[48231, 48242, 48253, 48264, 48275, 48286, 48297]` on first run; the website probes all candidates, so any pick is discoverable. Override with `--port`. |
|
|
218
|
+
| `harness` | Preferred session driver: `auto`, `opencode`, `claude`, `none` |
|
|
219
|
+
| `quietHours` | `{ "start": "22:00", "end": "07:00" }` — wake events are ignored during this window |
|
|
220
|
+
| `wakeRules` | `{ "priorities": ["high", "urgent"] }` — only wake for tasks matching these priorities |
|
|
221
|
+
|
|
222
|
+
## Environment variables
|
|
223
|
+
|
|
224
|
+
| Variable | Default | Used by |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `TEAMSHARE_API_URL` | `http://localhost:4000/api` | all commands |
|
|
227
|
+
| `TEAMSHARE_WS_URL` | `ws://localhost:4000` | daemon + live console + streaming |
|
|
228
|
+
| `LLM_BASE_URL` | (catalog per model) | `--self`, chat/doc replies |
|
|
229
|
+
| `LLM_API_KEY` | — (required for `--self`) | `--self`, chat/doc replies |
|
|
230
|
+
| `LLM_MODEL` | `deepseek-chat` | `--self` (catalog ids resolve the base URL) |
|
|
231
|
+
| `LLM_TEMPERATURE` | `0.2` | `--self`, chat/doc replies |
|
|
232
|
+
| `LLM_MAX_TOKENS` | — (provider default) | `--self`, chat/doc replies |
|
|
233
|
+
| `TEAMSHARE_ASK_TIMEOUT` | `900` (seconds) | `ask_human` wait limit |
|
|
234
|
+
| `TS_CHAT_ECHO` | — | Debug: post canned acknowledgments (deterministic E2E) |
|
|
235
|
+
|
|
236
|
+
## Live console & streaming
|
|
237
|
+
|
|
238
|
+
- Every session (`run`/`chat`/`doc`) opens its own `/agents` socket and emits
|
|
239
|
+
`session:start` / `session:output` (batched ~200ms) / `session:end` — the
|
|
240
|
+
app's **Live console** on the agent detail page shows the output in real
|
|
241
|
+
time, and a capped transcript is persisted server-side at the end.
|
|
242
|
+
- Local mirror + continuation history: `~/.teamshare/sessions/`
|
|
243
|
+
(`<sessionId>.json` transcripts, `task-<taskId>.json` for `--continue`).
|
|
244
|
+
- Chat replies additionally stream into the project channel as
|
|
245
|
+
`message:partial` frames over a `/chat` socket (apiKey auth) — the web UI
|
|
246
|
+
renders a live draft bubble that is swapped for the persisted message.
|
|
247
|
+
|
|
248
|
+
## The localhost probe
|
|
249
|
+
|
|
250
|
+
The website detects the bridge by calling `GET http://localhost:<port>/ping`
|
|
251
|
+
with permissive CORS (the browser can reach it from the web app):
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{ "ok": true, "version": "0.1.0", "agents": ["<agent-id>"] }
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
When the probe answers, the app shows "Bridge detected on this computer" and
|
|
258
|
+
enables the **Run agent** button; otherwise it degrades gracefully to the
|
|
259
|
+
copy-paste command.
|
|
260
|
+
|
|
261
|
+
## Security model
|
|
262
|
+
|
|
263
|
+
- **Agent API keys** (`ts_...`) are minted by TeamShare with scopes derived
|
|
264
|
+
from the agent's capabilities and can never exceed them; the raw key is
|
|
265
|
+
shown only once in the app. Store it via `connect` — it lives in
|
|
266
|
+
`~/.teamshare/config.json` (chmod 600 on unix).
|
|
267
|
+
- **Run tokens** in `teamshare://` deep links are short-lived (10 min),
|
|
268
|
+
single-use, and exchanged server-side for a session credential — the
|
|
269
|
+
long-lived key never appears in a URL.
|
|
270
|
+
- The bridge never sees environment-variable values; TeamShare's viewer-denied
|
|
271
|
+
rule extends to agents automatically.
|
|
272
|
+
- The probe server binds to `127.0.0.1` only.
|
|
273
|
+
|
|
274
|
+
## Platform notes
|
|
275
|
+
|
|
276
|
+
| Platform | Terminal spawn | Protocol registration |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| Windows | `cmd /c start cmd /k …` (detached window, stays open) | `HKCU\Software\Classes\teamshare` via `reg.exe`, per-user |
|
|
279
|
+
| macOS | Terminal.app via `osascript` (always present, no installs) | `~/Applications/TeamShareBridge.app` (CFBundleURLTypes); open the bundle once |
|
|
280
|
+
| Linux (desktop) | `xdg-terminal-exec` → `gnome-terminal` → `konsole` → `xterm` (first found on PATH) | `~/.local/share/applications/teamshare-bridge.desktop` (`x-scheme-handler/teamshare`) |
|
|
281
|
+
| Linux (server/SSH) | Headless fallback — no window; watch via the web live console | n/a |
|
|
282
|
+
|
|
283
|
+
## Troubleshooting
|
|
284
|
+
|
|
285
|
+
| Symptom | Fix |
|
|
286
|
+
|---|---|
|
|
287
|
+
| `EADDRINUSE` on probe | `--port <candidate>` or delete the `port` key from config; the website probes all candidates |
|
|
288
|
+
| "Agent not configured locally" on a deep link | Run `teamshare-agent connect --agent <id> --key ts_...` |
|
|
289
|
+
| Run button disabled with a connect hint | The agent's key isn't saved on this PC - run `connect`; keys are shown once (lost key = delete + recreate the agent) |
|
|
290
|
+
| Deep-link window flashes shut | The `serve` handler failed (usually a missing local key) - the error is now kept on screen via a 30s pause |
|
|
291
|
+
| No harness found on `run` | Install opencode or Claude, or use `--self` |
|
|
292
|
+
| Wake events never arrive | `teamshare-bridge status`; the daemon must be running and the key saved in config |
|
|
293
|
+
| Wakes queued but never spawn | Stale lock from a crashed session - `teamshare-bridge status` flags it, `clear-lock --agent <id>` removes it (dead locks auto-reclaim within seconds) |
|
|
294
|
+
| Session opens but exits instantly | Run `teamshare-agent run …` manually to see the error; check the API URL and key scopes; output is captured in `~/.teamshare/sessions/spawn-*.log` |
|
|
295
|
+
|
|
296
|
+
## Development
|
|
297
|
+
|
|
298
|
+
```sh
|
|
299
|
+
npm install
|
|
300
|
+
npm run build # tsc -> dist/
|
|
301
|
+
npm run typecheck # tsc --noEmit
|
|
302
|
+
npm run dev # watch mode
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Runtime dependency: `socket.io-client` only — everything else is Node
|
|
306
|
+
built-ins (fetch, http, crypto, child_process). Tests for the full loop run
|
|
307
|
+
against a live TeamShare API (see `docs/agent-setup.md`).
|
|
308
|
+
|
|
309
|
+
## Project layout
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
src/
|
|
313
|
+
├── lib/
|
|
314
|
+
│ ├── config.ts # ~/.teamshare/config.json (port candidates, wake rules)
|
|
315
|
+
│ ├── api.ts # typed REST client (envelope-aware, retries, timeouts)
|
|
316
|
+
│ ├── brief.ts # task brief fetcher + heartbeat/report helpers
|
|
317
|
+
│ ├── llm.ts # --self headless function-calling loop (+ SSE streaming)
|
|
318
|
+
│ ├── models.ts # curated <provider>:<model> catalog -> base URL resolution
|
|
319
|
+
│ ├── chat-reply.ts # chat reply loop (mention-driven, streaming)
|
|
320
|
+
│ ├── doc-reply.ts # Phase F document assistant reply loop
|
|
321
|
+
│ ├── session-stream.ts # live-console streaming over /agents + local transcripts
|
|
322
|
+
│ ├── partial-stream.ts # Phase C message:partial draft streaming over /chat
|
|
323
|
+
│ └── lock.ts # per-agent session lock (~/.teamshare/locks/<agentId>.lock)
|
|
324
|
+
├── cli/
|
|
325
|
+
│ └── index.ts # teamshare-agent (connect / run / chat / doc / continue)
|
|
326
|
+
└── bridge/
|
|
327
|
+
├── index.ts # teamshare-bridge CLI (daemon/register/serve/status/probe)
|
|
328
|
+
├── daemon.ts # WS auto-wake listener + wake rules + busy-queueing
|
|
329
|
+
├── local-server.ts# 127.0.0.1 probe server (/ping, /status)
|
|
330
|
+
├── spawn.ts # detached terminal session spawner (+ output capture)
|
|
331
|
+
└── protocol.ts # teamshare:// registration + deep-link dispatch
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Related docs
|
|
335
|
+
|
|
336
|
+
- `docs/agent-setup.md` — plain-language setup guide (mirrors the app's
|
|
337
|
+
Launch Console)
|
|
338
|
+
- `docs/agent-tasks/agent-hub-connection.md` (repo root) — the full agent
|
|
339
|
+
contract: REST endpoints, MCP tools, WS events, capabilities
|
|
340
|
+
- `docs/agent-models.md` (repo root) — curated model catalog + update process
|
|
341
|
+
- `docs/agent-capabilities.md` (repo root) — provider matrix + feature menu
|
|
342
|
+
- `docs/agent-roadmap-verification.md` (repo root) — step-by-step E2E playbook
|
|
343
|
+
- `AGENTS.md` (repo root) — project conventions
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDaemon(portOverride?: number): Promise<void>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDaemon = runDaemon;
|
|
4
|
+
/**
|
|
5
|
+
* `teamshare-bridge daemon` - keeps a WebSocket to TeamShare per configured
|
|
6
|
+
* agent and spawns a session when a `wake` event arrives (contract section 4).
|
|
7
|
+
* Also serves the localhost probe so the website can detect it.
|
|
8
|
+
*/
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const socket_io_client_1 = require("socket.io-client");
|
|
13
|
+
const api_1 = require("../lib/api");
|
|
14
|
+
const config_1 = require("../lib/config");
|
|
15
|
+
const lock_1 = require("../lib/lock");
|
|
16
|
+
const local_server_1 = require("./local-server");
|
|
17
|
+
const spawn_1 = require("./spawn");
|
|
18
|
+
const WS_DEFAULT = 'ws://localhost:4000';
|
|
19
|
+
const LOG_PATH = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'daemon.log');
|
|
20
|
+
function log(line) {
|
|
21
|
+
const stamp = new Date().toISOString();
|
|
22
|
+
const text = `[${stamp}] ${line}`;
|
|
23
|
+
console.log(text);
|
|
24
|
+
try {
|
|
25
|
+
(0, node_fs_1.appendFileSync)(LOG_PATH, text + '\n');
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
/* log file best-effort */
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function inQuietHours(config) {
|
|
32
|
+
if (!config.quietHours)
|
|
33
|
+
return false;
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const mins = now.getHours() * 60 + now.getMinutes();
|
|
36
|
+
const parse = (t) => {
|
|
37
|
+
const [h, m] = t.split(':').map(Number);
|
|
38
|
+
return h * 60 + (Number.isFinite(m) ? m : 0);
|
|
39
|
+
};
|
|
40
|
+
const start = parse(config.quietHours.start);
|
|
41
|
+
const end = parse(config.quietHours.end);
|
|
42
|
+
return start <= end ? mins >= start && mins < end : mins >= start || mins < end;
|
|
43
|
+
}
|
|
44
|
+
async function runDaemon(portOverride) {
|
|
45
|
+
const config = (0, config_1.loadConfig)();
|
|
46
|
+
const port = portOverride ?? config.port;
|
|
47
|
+
const apiBase = process.env.TEAMSHARE_API_URL ?? api_1.DEFAULT_API_URL;
|
|
48
|
+
const wsBase = (process.env.TEAMSHARE_WS_URL ?? WS_DEFAULT).replace(/\/$/, '');
|
|
49
|
+
/** agentId -> queued wakes waiting for the session lock to free up. */
|
|
50
|
+
const wakeQueue = new Map();
|
|
51
|
+
/** agentId -> ISO timestamp of the last wake event (probe/status). */
|
|
52
|
+
const lastWakeByAgent = new Map();
|
|
53
|
+
const getStatus = () => ({
|
|
54
|
+
configPath: (0, config_1.configPath)(),
|
|
55
|
+
agents: config.agents.map((a) => {
|
|
56
|
+
const lock = (0, lock_1.readLock)(a.agentId);
|
|
57
|
+
const base = {
|
|
58
|
+
agentId: a.agentId,
|
|
59
|
+
lastWake: lastWakeByAgent.get(a.agentId),
|
|
60
|
+
queued: wakeQueue.get(a.agentId)?.length ?? 0,
|
|
61
|
+
};
|
|
62
|
+
if ((0, lock_1.isLockBusy)(a.agentId) && lock) {
|
|
63
|
+
base.busy = true;
|
|
64
|
+
base.since = lock.startedAt;
|
|
65
|
+
base.kind = lock.kind;
|
|
66
|
+
base.taskId = lock.taskId;
|
|
67
|
+
base.projectId = lock.projectId;
|
|
68
|
+
base.documentId = lock.documentId;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
base.busy = false;
|
|
72
|
+
}
|
|
73
|
+
return base;
|
|
74
|
+
}),
|
|
75
|
+
registeredSchemes: false,
|
|
76
|
+
});
|
|
77
|
+
const { server, port: boundPort } = await (0, local_server_1.startProbeServer)(port, getStatus);
|
|
78
|
+
log(`probe server listening on http://localhost:${boundPort}/ping`);
|
|
79
|
+
if (config.agents.length === 0) {
|
|
80
|
+
log('no agents configured - run: teamshare-agent connect --agent <id> --key ts_...');
|
|
81
|
+
}
|
|
82
|
+
const sockets = [];
|
|
83
|
+
const timers = [];
|
|
84
|
+
/** agentId -> Phase D settings snapshot (refreshed on every reconnect). */
|
|
85
|
+
const settingsByAgent = new Map();
|
|
86
|
+
for (const agent of config.agents) {
|
|
87
|
+
const api = api_1.TeamshareApi.forAgent(apiBase, agent);
|
|
88
|
+
/** Keeps the agent marked online; errors are surfaced, never swallowed. */
|
|
89
|
+
const heartbeat = () => {
|
|
90
|
+
api
|
|
91
|
+
.heartbeat(agent.agentId, 'online')
|
|
92
|
+
.then(() => log(`agent ${agent.agentId}: heartbeat online`))
|
|
93
|
+
.catch((err) => log(`agent ${agent.agentId}: heartbeat failed: ${(0, config_1.msg)(err)}`));
|
|
94
|
+
};
|
|
95
|
+
const socket = (0, socket_io_client_1.io)(`${wsBase}/agents`, {
|
|
96
|
+
transports: ['websocket'],
|
|
97
|
+
auth: { apiKey: agent.apiKey },
|
|
98
|
+
reconnection: true,
|
|
99
|
+
reconnectionDelay: 2_000,
|
|
100
|
+
reconnectionDelayMax: 30_000,
|
|
101
|
+
});
|
|
102
|
+
socket.on('connect', () => {
|
|
103
|
+
log(`agent ${agent.agentId}: connected to /agents`);
|
|
104
|
+
// The gateway authenticates asynchronously after the socket connects -
|
|
105
|
+
// retry the join until the ack confirms client.agentId is set (which
|
|
106
|
+
// also carries the Phase D settings snapshot).
|
|
107
|
+
const joinUntilReady = (attempt) => {
|
|
108
|
+
socket.emit('join', { ack: true }, (res) => {
|
|
109
|
+
if (res?.success) {
|
|
110
|
+
const settings = res.data?.settings ?? null;
|
|
111
|
+
settingsByAgent.set(agent.agentId, settings);
|
|
112
|
+
if (settings) {
|
|
113
|
+
log(`agent ${agent.agentId}: settings ${JSON.stringify({
|
|
114
|
+
model: settings.model ?? null,
|
|
115
|
+
temperature: settings.temperature ?? null,
|
|
116
|
+
maxTokens: settings.maxTokens ?? null,
|
|
117
|
+
})}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (attempt < 10) {
|
|
121
|
+
setTimeout(() => joinUntilReady(attempt + 1), 1000);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
log(`agent ${agent.agentId}: join ack failed after retries`);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
joinUntilReady(0);
|
|
129
|
+
heartbeat();
|
|
130
|
+
timers.push(setInterval(heartbeat, 60_000));
|
|
131
|
+
});
|
|
132
|
+
socket.on('disconnect', (reason) => {
|
|
133
|
+
log(`agent ${agent.agentId}: disconnected (${reason})`);
|
|
134
|
+
});
|
|
135
|
+
socket.on('connect_error', (err) => {
|
|
136
|
+
log(`agent ${agent.agentId}: connect_error ${(0, config_1.msg)(err)}`);
|
|
137
|
+
});
|
|
138
|
+
socket.on('wake', (event) => {
|
|
139
|
+
lastWakeByAgent.set(agent.agentId, new Date().toISOString());
|
|
140
|
+
void handleWake(api, agent.agentId, agent.apiKey, event, config, {
|
|
141
|
+
settings: settingsByAgent.get(agent.agentId),
|
|
142
|
+
wakeQueue,
|
|
143
|
+
}).catch((err) => log(`agent ${agent.agentId}: wake failed: ${(0, config_1.msg)(err)}`));
|
|
144
|
+
});
|
|
145
|
+
sockets.push(socket);
|
|
146
|
+
wakeQueue.set(agent.agentId, []);
|
|
147
|
+
}
|
|
148
|
+
// Queue flush: spawn queued wakes once the agent's session lock frees up
|
|
149
|
+
// (staleness-aware - a crashed session's dead-pid lock is treated as free).
|
|
150
|
+
const flushInterval = setInterval(() => {
|
|
151
|
+
for (const [agentId, queue] of wakeQueue) {
|
|
152
|
+
if (queue.length === 0)
|
|
153
|
+
continue;
|
|
154
|
+
if ((0, lock_1.isLockBusy)(agentId))
|
|
155
|
+
continue; // still busy - keep waiting
|
|
156
|
+
const agent = config.agents.find((a) => a.agentId === agentId);
|
|
157
|
+
const event = queue.shift();
|
|
158
|
+
if (!agent || !event)
|
|
159
|
+
continue;
|
|
160
|
+
void spawnWake(api_1.TeamshareApi.forAgent(apiBase, agent), agentId, agent.apiKey, event, config, { settings: settingsByAgent.get(agentId) }).catch((err) => log(`agent ${agentId}: queued wake failed: ${(0, config_1.msg)(err)}`));
|
|
161
|
+
log(`agent ${agentId}: spawned queued wake ${event.type}`);
|
|
162
|
+
}
|
|
163
|
+
}, lock_1.LOCK_POLL_MS);
|
|
164
|
+
timers.push(flushInterval);
|
|
165
|
+
const shutdown = () => {
|
|
166
|
+
log('shutting down');
|
|
167
|
+
for (const t of timers)
|
|
168
|
+
clearInterval(t);
|
|
169
|
+
for (const s of sockets)
|
|
170
|
+
s.close();
|
|
171
|
+
server.close();
|
|
172
|
+
process.exit(0);
|
|
173
|
+
};
|
|
174
|
+
process.on('SIGINT', shutdown);
|
|
175
|
+
process.on('SIGTERM', shutdown);
|
|
176
|
+
}
|
|
177
|
+
async function handleWake(api, agentId, apiKey, event, config, extra) {
|
|
178
|
+
const { wakeQueue, settings } = extra;
|
|
179
|
+
if (inQuietHours(config)) {
|
|
180
|
+
log(`agent ${agentId}: wake ${event.type} skipped (quiet hours)`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (event.taskId && config.wakeRules?.priorities?.length) {
|
|
184
|
+
try {
|
|
185
|
+
const task = await api.getTask(event.taskId);
|
|
186
|
+
if (!config.wakeRules.priorities.includes(task.priority)) {
|
|
187
|
+
log(`agent ${agentId}: wake ${event.type} skipped (priority ${task.priority})`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
/* fetch failed - proceed anyway */
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const lock = (0, lock_1.readLock)(agentId);
|
|
196
|
+
if ((0, lock_1.isLockBusy)(agentId) && lock) {
|
|
197
|
+
// The agent is busy with a live session (daemon-spawned or manual
|
|
198
|
+
// terminal) - queue this wake; the flush interval spawns it once the
|
|
199
|
+
// lock frees up (dead-pid locks are treated as free immediately).
|
|
200
|
+
const queue = wakeQueue.get(agentId) ?? [];
|
|
201
|
+
queue.push(event);
|
|
202
|
+
wakeQueue.set(agentId, queue);
|
|
203
|
+
log(`agent ${agentId}: wake ${event.type} queued (busy since ${lock.startedAt}, ${lock.kind}) - ${queue.length} waiting`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
await spawnWake(api, agentId, apiKey, event, config, { settings });
|
|
207
|
+
}
|
|
208
|
+
async function spawnWake(api, agentId, apiKey, event, config, extra = {}) {
|
|
209
|
+
log(`agent ${agentId}: wake ${event.type}${event.taskId ? ` task=${event.taskId}` : ''} - spawning session`);
|
|
210
|
+
if (event.type === 'chat.mention') {
|
|
211
|
+
if (!event.projectId) {
|
|
212
|
+
log(`agent ${agentId}: chat.mention without projectId - skipping`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Chat mentions spawn a chat-reply loop (no task context).
|
|
216
|
+
(0, spawn_1.spawnChatSession)(agentId, apiKey, event.projectId, event.mention ?? '', extra.settings);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (event.type === 'document.mention') {
|
|
220
|
+
if (!event.documentId) {
|
|
221
|
+
log(`agent ${agentId}: document.mention without documentId - skipping`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Document mentions spawn the document assistant loop (Phase F).
|
|
225
|
+
(0, spawn_1.spawnDocumentSession)(agentId, apiKey, event.documentId, event.mention ?? '', extra.settings);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
(0, spawn_1.spawnAgentSession)(agentId, apiKey, event.taskId ?? '', extra.settings);
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/bridge/daemon.ts"],"names":[],"mappings":"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"}
|