agent-office 0.0.12 → 0.0.13
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 +235 -64
- package/dist/commands/serve.js +5 -5
- package/dist/lib/agentic-coding-server.d.ts +58 -0
- package/dist/lib/agentic-coding-server.js +7 -0
- package/dist/lib/opencode-coding-server.d.ts +11 -0
- package/dist/lib/opencode-coding-server.js +56 -0
- package/dist/server/cron.d.ts +3 -3
- package/dist/server/cron.js +6 -10
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +3 -3
- package/dist/server/routes.d.ts +3 -3
- package/dist/server/routes.js +31 -69
- package/package.json +3 -2
- package/dist/lib/opencode.d.ts +0 -7
- package/dist/lib/opencode.js +0 -5
package/README.md
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
1
|
# agent-office
|
|
2
2
|
|
|
3
|
-
Manage [OpenCode](https://opencode.ai) sessions
|
|
3
|
+
An office for your AI agents. Manage multiple [OpenCode](https://opencode.ai) coding sessions as named coworkers with inter-agent messaging, scheduled tasks, persistent memory, and a terminal UI for human oversight.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
`agent-office`
|
|
7
|
+
`agent-office` sits between a human operator and one or more AI coding agents running on an OpenCode server. It wraps raw OpenCode sessions with named identities, a messaging system, cron-based scheduling, and per-agent memory -- creating an "office" where AI agents are coworkers that communicate, take direction, and operate autonomously.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **`
|
|
9
|
+
**For humans**, there are three interfaces:
|
|
10
|
+
|
|
11
|
+
- **`serve`** -- an HTTP server that manages sessions, messages, cron jobs, and memory, backed by PostgreSQL and a running OpenCode server
|
|
12
|
+
- **`manage`** -- a full-screen terminal UI (React Ink) for creating coworkers, sending messages, browsing mail, managing cron jobs, and observing agent activity
|
|
13
|
+
- **`communicator web`** -- a browser-based chat interface for conversing with a specific agent in real time
|
|
14
|
+
|
|
15
|
+
**For AI agents**, there is a CLI:
|
|
16
|
+
|
|
17
|
+
- **`worker`** -- subcommands that agents invoke from within their OpenCode sessions to clock in, message coworkers, set status, manage cron jobs, and store memories
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
+---------------------+
|
|
21
|
+
| OpenCode Server |
|
|
22
|
+
| :4096 |
|
|
23
|
+
+---------+-----------+
|
|
24
|
+
|
|
|
25
|
+
+--------------+ +---------+-----------+ +--------------+
|
|
26
|
+
| TUI (manage) |<-->| agent-office serve |<-->| PostgreSQL |
|
|
27
|
+
| Ink/React | | :7654 | | |
|
|
28
|
+
+--------------+ | | +--------------+
|
|
29
|
+
| CronScheduler |
|
|
30
|
+
+--------------+ | MemoryManager | +--------------+
|
|
31
|
+
| Communicator |<-->| |<-->| .memory/ |
|
|
32
|
+
| Web (HTMX) | | | | SQLite DBs |
|
|
33
|
+
| :7655 | +---------+-----------+ +--------------+
|
|
34
|
+
+--------------+ |
|
|
35
|
+
| /worker/* endpoints
|
|
36
|
+
+---------+-----------+
|
|
37
|
+
| AI Agent Workers |
|
|
38
|
+
| (running inside |
|
|
39
|
+
| OpenCode sessions) |
|
|
40
|
+
+-----------------------+
|
|
41
|
+
```
|
|
12
42
|
|
|
13
43
|
## Installation
|
|
14
44
|
|
|
@@ -19,7 +49,7 @@ npm install -g agent-office
|
|
|
19
49
|
Or run without installing:
|
|
20
50
|
|
|
21
51
|
```bash
|
|
22
|
-
npx agent-office
|
|
52
|
+
npx agent-office --help
|
|
23
53
|
```
|
|
24
54
|
|
|
25
55
|
## Requirements
|
|
@@ -36,31 +66,60 @@ npx agent-office serve --help
|
|
|
36
66
|
agent-office serve \
|
|
37
67
|
--database-url "postgresql://user:pass@localhost:5432/mydb" \
|
|
38
68
|
--opencode-url "http://localhost:4096" \
|
|
39
|
-
--host 127.0.0.1 \
|
|
40
|
-
--port 7654 \
|
|
41
69
|
--password mysecret
|
|
42
70
|
```
|
|
43
71
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
export DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
|
|
48
|
-
export AGENT_OFFICE_PASSWORD="mysecret"
|
|
49
|
-
agent-office serve
|
|
50
|
-
```
|
|
72
|
+
The server runs migrations automatically on startup, initializes the cron scheduler, and warms up the embedding model for agent memory.
|
|
51
73
|
|
|
52
74
|
### 2. Open the manager
|
|
53
75
|
|
|
76
|
+
In another terminal:
|
|
77
|
+
|
|
54
78
|
```bash
|
|
55
|
-
agent-office manage
|
|
79
|
+
agent-office manage --password mysecret
|
|
56
80
|
```
|
|
57
81
|
|
|
58
|
-
|
|
82
|
+
From the TUI you can create coworkers, send them messages, browse mail, manage cron jobs, and observe their sessions.
|
|
83
|
+
|
|
84
|
+
### 3. Chat with a coworker in the browser
|
|
59
85
|
|
|
60
86
|
```bash
|
|
61
|
-
agent-office
|
|
87
|
+
agent-office communicator web "Alice" --secret mysecret
|
|
62
88
|
```
|
|
63
89
|
|
|
90
|
+
Opens a web-based chat interface at `http://127.0.0.1:7655` for real-time conversation with the named coworker.
|
|
91
|
+
|
|
92
|
+
## How It Works
|
|
93
|
+
|
|
94
|
+
### Session Lifecycle
|
|
95
|
+
|
|
96
|
+
1. **Create** -- A human creates a named coworker via the TUI or API. This creates an OpenCode session, stores the mapping in PostgreSQL, and sends an enrollment message containing a clock-in command.
|
|
97
|
+
|
|
98
|
+
2. **Clock In** -- The AI agent sees the enrollment message in its session, runs `agent-office worker clock-in <token>`, and receives a welcome briefing with its name, the human manager's identity, all available CLI commands, and a privacy notice.
|
|
99
|
+
|
|
100
|
+
3. **Work** -- The agent operates in its OpenCode session. It communicates with the human and other agents by running `agent-office worker send-message`. It can set its status, create cron jobs, and store persistent memories.
|
|
101
|
+
|
|
102
|
+
4. **Message Delivery** -- Messages are stored in PostgreSQL and simultaneously injected as prompts into the recipient's OpenCode session. This means agents see messages immediately in their active session context.
|
|
103
|
+
|
|
104
|
+
5. **Reset** -- A session can be reverted to its initial state (clearing all conversation history) and re-enrolled. Memories persist across resets since they are stored separately.
|
|
105
|
+
|
|
106
|
+
6. **Delete** -- Deleting a coworker removes both the OpenCode session and the database record.
|
|
107
|
+
|
|
108
|
+
### Privacy Model
|
|
109
|
+
|
|
110
|
+
Agent sessions are private by design. The welcome message tells each agent:
|
|
111
|
+
|
|
112
|
+
> Nobody -- not your human manager, not your coworkers -- can see anything you think, reason, or write inside this session. Your work is completely private until you explicitly send a message.
|
|
113
|
+
|
|
114
|
+
This means agents must actively communicate to report progress, ask questions, or share results.
|
|
115
|
+
|
|
116
|
+
### Authentication
|
|
117
|
+
|
|
118
|
+
Two authentication schemes run in parallel:
|
|
119
|
+
|
|
120
|
+
- **Bearer token** -- Human operators authenticate with `Authorization: Bearer <password>` for all management endpoints.
|
|
121
|
+
- **Agent code** -- AI agents authenticate with a UUID passed as `?code=<uuid>` for all `/worker/*` endpoints. Each agent gets a unique code generated at session creation time.
|
|
122
|
+
|
|
64
123
|
## Environment Variables
|
|
65
124
|
|
|
66
125
|
Copy `.env.example` to `.env` for local development:
|
|
@@ -69,81 +128,191 @@ Copy `.env.example` to `.env` for local development:
|
|
|
69
128
|
cp .env.example .env
|
|
70
129
|
```
|
|
71
130
|
|
|
72
|
-
| Variable | Description |
|
|
73
|
-
|
|
74
|
-
| `DATABASE_URL` | PostgreSQL connection string |
|
|
75
|
-
| `AGENT_OFFICE_PASSWORD` |
|
|
76
|
-
| `OPENCODE_URL` | OpenCode server URL
|
|
131
|
+
| Variable | Description | Default |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `DATABASE_URL` | PostgreSQL connection string | (required) |
|
|
134
|
+
| `AGENT_OFFICE_PASSWORD` | API password for human access | (required) |
|
|
135
|
+
| `OPENCODE_URL` | OpenCode server URL | `http://localhost:4096` |
|
|
136
|
+
| `AGENT_OFFICE_URL` | Server URL for manage/communicator clients | `http://127.0.0.1:7654` |
|
|
77
137
|
|
|
78
138
|
## Commands
|
|
79
139
|
|
|
80
140
|
### `agent-office serve`
|
|
81
141
|
|
|
82
|
-
Starts the HTTP server.
|
|
142
|
+
Starts the HTTP server. Connects to PostgreSQL, runs migrations, initializes the cron scheduler, and warms up the embedding model.
|
|
83
143
|
|
|
84
144
|
```
|
|
85
145
|
Options:
|
|
86
|
-
--database-url <url>
|
|
87
|
-
--opencode-url <url>
|
|
88
|
-
--host <host>
|
|
89
|
-
--port <port>
|
|
90
|
-
--
|
|
146
|
+
--database-url <url> PostgreSQL connection string (env: DATABASE_URL)
|
|
147
|
+
--opencode-url <url> OpenCode server URL (default: http://localhost:4096)
|
|
148
|
+
--host <host> Bind host (default: 127.0.0.1)
|
|
149
|
+
--port <port> Bind port (default: 7654)
|
|
150
|
+
--memory-path <path> Directory for memory storage (default: ./.memory)
|
|
151
|
+
--password <password> REQUIRED. API password (env: AGENT_OFFICE_PASSWORD)
|
|
91
152
|
```
|
|
92
153
|
|
|
93
|
-
### `agent-office manage
|
|
154
|
+
### `agent-office manage`
|
|
94
155
|
|
|
95
|
-
Opens the full-screen terminal UI. All operations go through the server
|
|
156
|
+
Opens the full-screen terminal UI. All operations go through the HTTP server -- the TUI never touches PostgreSQL or OpenCode directly.
|
|
96
157
|
|
|
97
158
|
```
|
|
98
|
-
Arguments:
|
|
99
|
-
url URL of the agent-office server
|
|
100
|
-
|
|
101
159
|
Options:
|
|
102
|
-
--
|
|
160
|
+
--url <url> Server URL (default: http://127.0.0.1:7654)
|
|
161
|
+
--password <password> REQUIRED. API password (env: AGENT_OFFICE_PASSWORD)
|
|
103
162
|
```
|
|
104
163
|
|
|
105
|
-
#### TUI
|
|
164
|
+
#### TUI Features
|
|
106
165
|
|
|
107
166
|
| Screen | Description |
|
|
108
167
|
|---|---|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
| Agent code | Reveal, hide, or regenerate the agent code for a session |
|
|
168
|
+
| **Coworkers** | Table of all agents showing name, status, mode, session ID, and masked agent code. Keyboard: `c` create, `d` delete, `r` reveal code, `g` regenerate code, `x` revert session, `X` revert all, `t` tail messages, `i` inject text, `m` coworker mail, `M` memories |
|
|
169
|
+
| **Send message** | Select a recipient and compose a message |
|
|
170
|
+
| **My mail** | View received and sent messages. `r` reply, `m` mark read, `a` mark all read. Tab between received/sent |
|
|
171
|
+
| **Cron jobs** | Table of scheduled tasks with name, coworker, schedule, next run, and status. Create, delete, enable/disable, view history |
|
|
172
|
+
| **My profile** | Set your display name and description (visible to agents in their welcome message) |
|
|
115
173
|
|
|
116
|
-
|
|
174
|
+
A sidebar shows all coworkers with live status indicators, refreshed every 5 seconds. An unread mail badge appears in the header.
|
|
117
175
|
|
|
118
|
-
|
|
176
|
+
### `agent-office communicator web <coworker>`
|
|
119
177
|
|
|
120
|
-
|
|
121
|
-
agent-office worker clock-in <agent_code>@<server-url>
|
|
178
|
+
Launches a browser-based chat interface for real-time conversation with a specific agent.
|
|
122
179
|
|
|
123
|
-
# Example
|
|
124
|
-
agent-office worker clock-in 550e8400-e29b-41d4-a716-446655440000@http://localhost:7654
|
|
125
180
|
```
|
|
181
|
+
Arguments:
|
|
182
|
+
<coworker> Name of the coworker to chat with
|
|
126
183
|
|
|
127
|
-
|
|
128
|
-
|
|
184
|
+
Options:
|
|
185
|
+
--url <url> Server URL (default: http://127.0.0.1:7654)
|
|
186
|
+
--secret <secret> API password (env: AGENT_OFFICE_PASSWORD)
|
|
187
|
+
--host <host> Communicator bind host (default: 127.0.0.1)
|
|
188
|
+
--port <port> Communicator bind port (default: 7655)
|
|
129
189
|
```
|
|
130
|
-
|
|
190
|
+
|
|
191
|
+
Features: dark theme, iMessage-style chat bubbles, auto-scroll, Enter to send (Shift+Enter for newline), live message polling (5s), unread indicators, status display, and a reset button to revert the agent's session.
|
|
192
|
+
|
|
193
|
+
### `agent-office worker` (for AI agents)
|
|
194
|
+
|
|
195
|
+
All worker commands authenticate via a token in the format `<agent_code>@<server_url>`.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Clock in and receive your briefing
|
|
199
|
+
agent-office worker clock-in <token>
|
|
200
|
+
|
|
201
|
+
# List coworkers and the human manager
|
|
202
|
+
agent-office worker list-coworkers <token>
|
|
203
|
+
|
|
204
|
+
# Set or clear your public status (max 140 chars)
|
|
205
|
+
agent-office worker set-status --status "Working on auth module" <token>
|
|
206
|
+
agent-office worker set-status --clear <token>
|
|
207
|
+
|
|
208
|
+
# Send a message to one or more recipients
|
|
209
|
+
agent-office worker send-message --name Alice --name Bob --body "Status update: PR is ready" <token>
|
|
210
|
+
|
|
211
|
+
# Cron job management
|
|
212
|
+
agent-office worker cron list <token>
|
|
213
|
+
agent-office worker cron create --name "daily-report" --schedule "0 9 * * *" --message "Send daily status" <token>
|
|
214
|
+
agent-office worker cron create --name "weekly" --schedule "0 9 * * 1" --message "Weekly sync" --timezone "America/New_York" <token>
|
|
215
|
+
agent-office worker cron delete <token> <id>
|
|
216
|
+
agent-office worker cron enable <token> <id>
|
|
217
|
+
agent-office worker cron disable <token> <id>
|
|
218
|
+
agent-office worker cron history <token> <id>
|
|
219
|
+
|
|
220
|
+
# Persistent memory (survives session resets)
|
|
221
|
+
agent-office worker memory add --content "The auth module uses JWT with RS256" <token>
|
|
222
|
+
agent-office worker memory search --query "authentication" <token>
|
|
223
|
+
agent-office worker memory list <token>
|
|
224
|
+
agent-office worker memory forget <token> <memory-id>
|
|
131
225
|
```
|
|
132
226
|
|
|
133
227
|
## REST API
|
|
134
228
|
|
|
135
|
-
|
|
229
|
+
### Authenticated Endpoints (Bearer token required)
|
|
136
230
|
|
|
137
231
|
| Method | Path | Description |
|
|
138
232
|
|---|---|---|
|
|
139
233
|
| `GET` | `/health` | Health check |
|
|
234
|
+
| `GET` | `/modes` | List available agent modes from the OpenCode server |
|
|
140
235
|
| `GET` | `/sessions` | List all sessions |
|
|
141
|
-
| `POST` | `/sessions` | Create session `{ name }` |
|
|
142
|
-
| `DELETE` | `/sessions/:name` | Delete session
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `POST` | `/sessions/:name/
|
|
146
|
-
| `
|
|
236
|
+
| `POST` | `/sessions` | Create a session. Body: `{ name, agent? }` |
|
|
237
|
+
| `DELETE` | `/sessions/:name` | Delete a session |
|
|
238
|
+
| `POST` | `/sessions/:name/regenerate-code` | Regenerate the agent code UUID |
|
|
239
|
+
| `GET` | `/sessions/:name/messages` | Fetch messages. Query: `?limit=N` (max 100) |
|
|
240
|
+
| `POST` | `/sessions/:name/inject` | Inject text. Body: `{ text }` |
|
|
241
|
+
| `POST` | `/sessions/:name/revert-to-start` | Revert session and re-enroll |
|
|
242
|
+
| `POST` | `/sessions/revert-all` | Revert all sessions |
|
|
243
|
+
| `GET` | `/config` | Get all config values |
|
|
244
|
+
| `PUT` | `/config` | Set a config value. Body: `{ key, value }` |
|
|
245
|
+
| `GET` | `/messages/:name` | Get messages for a person. Query: `?sent=true`, `?unread_only=true` |
|
|
246
|
+
| `POST` | `/messages` | Send a message. Body: `{ from, to: string[], body }` |
|
|
247
|
+
| `POST` | `/messages/:id/read` | Mark a message as read |
|
|
248
|
+
| `GET` | `/crons` | List cron jobs. Query: `?session_name=<name>` |
|
|
249
|
+
| `POST` | `/crons` | Create a cron job. Body: `{ name, session_name, schedule, message, timezone? }` |
|
|
250
|
+
| `DELETE` | `/crons/:id` | Delete a cron job |
|
|
251
|
+
| `POST` | `/crons/:id/enable` | Enable a cron job |
|
|
252
|
+
| `POST` | `/crons/:id/disable` | Disable a cron job |
|
|
253
|
+
| `GET` | `/crons/:id/history` | Cron execution history. Query: `?limit=N` |
|
|
254
|
+
| `GET` | `/sessions/:name/memories` | List memories. Query: `?limit=N` |
|
|
255
|
+
| `POST` | `/sessions/:name/memories` | Add a memory. Body: `{ content, metadata? }` |
|
|
256
|
+
| `GET` | `/sessions/:name/memories/:id` | Get a memory |
|
|
257
|
+
| `PUT` | `/sessions/:name/memories/:id` | Update a memory. Body: `{ content, metadata? }` |
|
|
258
|
+
| `DELETE` | `/sessions/:name/memories/:id` | Delete a memory |
|
|
259
|
+
| `POST` | `/sessions/:name/memories/search` | Search memories. Body: `{ query, limit? }` |
|
|
260
|
+
|
|
261
|
+
### Worker Endpoints (agent code auth via `?code=<uuid>`)
|
|
262
|
+
|
|
263
|
+
| Method | Path | Description |
|
|
264
|
+
|---|---|---|
|
|
265
|
+
| `GET` | `/worker/clock-in` | Clock in and receive welcome briefing |
|
|
266
|
+
| `GET` | `/worker/list-coworkers` | List all other agents and the human manager |
|
|
267
|
+
| `POST` | `/worker/set-status` | Set or clear status. Body: `{ status }` |
|
|
268
|
+
| `POST` | `/worker/send-message` | Send a message. Body: `{ to: string[], body }` |
|
|
269
|
+
| `GET` | `/worker/crons` | List own cron jobs |
|
|
270
|
+
| `POST` | `/worker/crons` | Create a cron job |
|
|
271
|
+
| `DELETE` | `/worker/crons/:id` | Delete own cron job |
|
|
272
|
+
| `POST` | `/worker/crons/:id/enable` | Enable own cron job |
|
|
273
|
+
| `POST` | `/worker/crons/:id/disable` | Disable own cron job |
|
|
274
|
+
| `GET` | `/worker/crons/:id/history` | View own cron job history |
|
|
275
|
+
| `POST` | `/worker/memory/add` | Add a memory |
|
|
276
|
+
| `POST` | `/worker/memory/search` | Search memories |
|
|
277
|
+
| `GET` | `/worker/memory/list` | List memories |
|
|
278
|
+
| `DELETE` | `/worker/memory/:memoryId` | Delete a memory |
|
|
279
|
+
|
|
280
|
+
## Architecture
|
|
281
|
+
|
|
282
|
+
### Database Schema
|
|
283
|
+
|
|
284
|
+
The server uses PostgreSQL with automatic migrations. Tables:
|
|
285
|
+
|
|
286
|
+
- **`sessions`** -- Maps coworker names to OpenCode session IDs with agent codes, agent modes, and status
|
|
287
|
+
- **`config`** -- Key-value store for application settings (`human_name`, `human_description`)
|
|
288
|
+
- **`messages`** -- Inter-agent and human-agent mail with read/injected tracking
|
|
289
|
+
- **`cron_jobs`** -- Scheduled tasks tied to sessions with cron expressions and timezone support
|
|
290
|
+
- **`cron_history`** -- Execution log for cron jobs with success/failure tracking
|
|
291
|
+
|
|
292
|
+
### Memory System
|
|
293
|
+
|
|
294
|
+
Each agent gets a private SQLite database (via [fastmemory](https://github.com/nichochar/fastmemory)) stored in the `--memory-path` directory. Memories support:
|
|
295
|
+
|
|
296
|
+
- **Hybrid search** -- BM25 full-text search combined with vector semantic search using Reciprocal Rank Fusion
|
|
297
|
+
- **Persistence across resets** -- Memories are stored outside the OpenCode session, so they survive session reverts
|
|
298
|
+
- **Quantized embeddings** -- Uses `q4` dtype for efficient storage
|
|
299
|
+
|
|
300
|
+
### Agentic Coding Server Abstraction
|
|
301
|
+
|
|
302
|
+
The application does not depend on the OpenCode SDK directly. Instead, all OpenCode interactions go through an `AgenticCodingServer` interface (`src/lib/agentic-coding-server.ts`) with six methods:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
interface AgenticCodingServer {
|
|
306
|
+
createSession(): Promise<string>
|
|
307
|
+
deleteSession(sessionID: string): Promise<void>
|
|
308
|
+
sendMessage(sessionID: string, text: string, agent?: string): Promise<void>
|
|
309
|
+
getMessages(sessionID: string, limit?: number): Promise<SessionMessage[]>
|
|
310
|
+
revertSession(sessionID: string, messageID: string): Promise<void>
|
|
311
|
+
getAgentModes(): Promise<AgentMode[]>
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The concrete `OpenCodeCodingServer` implementation (`src/lib/opencode-coding-server.ts`) encapsulates all SDK interactions. Only the server entrypoint (`src/commands/serve.ts`) knows the concrete class -- everything else depends on the interface.
|
|
147
316
|
|
|
148
317
|
## Development
|
|
149
318
|
|
|
@@ -151,15 +320,17 @@ All endpoints except `/worker/clock-in` require `Authorization: Bearer <password
|
|
|
151
320
|
# Install dependencies
|
|
152
321
|
npm install
|
|
153
322
|
|
|
154
|
-
# Copy
|
|
323
|
+
# Copy and edit environment
|
|
155
324
|
cp .env.example .env
|
|
156
|
-
# Edit .env with your values
|
|
157
325
|
|
|
158
|
-
# Run server
|
|
326
|
+
# Run server with hot reload
|
|
159
327
|
npm run dev:serve -- --password secret
|
|
160
328
|
|
|
161
|
-
# Run
|
|
162
|
-
npm run dev:manage --
|
|
329
|
+
# Run TUI (in another terminal)
|
|
330
|
+
npm run dev:manage -- --password secret
|
|
331
|
+
|
|
332
|
+
# Run communicator (in another terminal)
|
|
333
|
+
npm run dev:communicator -- "Alice" --secret secret
|
|
163
334
|
|
|
164
335
|
# Build
|
|
165
336
|
npm run build
|
|
@@ -167,4 +338,4 @@ npm run build
|
|
|
167
338
|
|
|
168
339
|
## License
|
|
169
340
|
|
|
170
|
-
MIT
|
|
341
|
+
MIT
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createDb } from "../db/index.js";
|
|
2
2
|
import { runMigrations } from "../db/migrate.js";
|
|
3
|
-
import {
|
|
3
|
+
import { OpenCodeCodingServer } from "../lib/opencode-coding-server.js";
|
|
4
4
|
import { createApp } from "../server/index.js";
|
|
5
5
|
import { CronScheduler } from "../server/cron.js";
|
|
6
6
|
import { MemoryManager } from "../server/memory.js";
|
|
@@ -34,8 +34,8 @@ export async function serve(options) {
|
|
|
34
34
|
await sql.end();
|
|
35
35
|
process.exit(1);
|
|
36
36
|
}
|
|
37
|
-
// Init OpenCode
|
|
38
|
-
const
|
|
37
|
+
// Init agentic coding server (OpenCode implementation)
|
|
38
|
+
const agenticCodingServer = new OpenCodeCodingServer(options.opencodeUrl);
|
|
39
39
|
const serverUrl = `http://${options.host}:${port}`;
|
|
40
40
|
// Create memory manager and verify embedding model
|
|
41
41
|
const memoryManager = new MemoryManager(options.memoryPath);
|
|
@@ -55,9 +55,9 @@ export async function serve(options) {
|
|
|
55
55
|
// Create cron scheduler
|
|
56
56
|
const cronScheduler = new CronScheduler();
|
|
57
57
|
// Create Express app
|
|
58
|
-
const app = createApp(sql,
|
|
58
|
+
const app = createApp(sql, agenticCodingServer, password, serverUrl, cronScheduler, memoryManager);
|
|
59
59
|
// Start cron scheduler
|
|
60
|
-
await cronScheduler.start(sql,
|
|
60
|
+
await cronScheduler.start(sql, agenticCodingServer);
|
|
61
61
|
// Start server
|
|
62
62
|
const server = app.listen(port, options.host, () => {
|
|
63
63
|
console.log(`agent-office server listening on ${serverUrl}`);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgenticCodingServer — abstract interface for interacting with an agentic
|
|
3
|
+
* coding backend. The application code depends only on this interface;
|
|
4
|
+
* concrete implementations (e.g. OpenCodeCodingServer) supply the wiring
|
|
5
|
+
* to a specific server.
|
|
6
|
+
*/
|
|
7
|
+
/** Minimal representation of a message part. */
|
|
8
|
+
export interface MessagePart {
|
|
9
|
+
type: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** A single message inside a session. */
|
|
13
|
+
export interface SessionMessage {
|
|
14
|
+
id: string;
|
|
15
|
+
role: string;
|
|
16
|
+
parts: MessagePart[];
|
|
17
|
+
}
|
|
18
|
+
/** An agent mode advertised by the server. */
|
|
19
|
+
export interface AgentMode {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
model: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AgenticCodingServer {
|
|
25
|
+
/**
|
|
26
|
+
* Create a new session.
|
|
27
|
+
* @returns the server-assigned session ID.
|
|
28
|
+
*/
|
|
29
|
+
createSession(): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Permanently delete a session.
|
|
32
|
+
*/
|
|
33
|
+
deleteSession(sessionID: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Inject a text message into a session (fire-and-forget).
|
|
36
|
+
* The server processes the message asynchronously.
|
|
37
|
+
*
|
|
38
|
+
* @param agent optional agent-mode identifier to route the prompt.
|
|
39
|
+
*/
|
|
40
|
+
sendMessage(sessionID: string, text: string, agent?: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Retrieve the message history for a session.
|
|
43
|
+
*
|
|
44
|
+
* @param limit maximum number of messages to return (server may cap this).
|
|
45
|
+
*/
|
|
46
|
+
getMessages(sessionID: string, limit?: number): Promise<SessionMessage[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Reset a session to a specific message.
|
|
49
|
+
*
|
|
50
|
+
* Implementations should abort any in-progress generation before reverting
|
|
51
|
+
* so callers don't need to worry about "session is busy" errors.
|
|
52
|
+
*/
|
|
53
|
+
revertSession(sessionID: string, messageID: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Return the list of agent modes the server knows about.
|
|
56
|
+
*/
|
|
57
|
+
getAgentModes(): Promise<AgentMode[]>;
|
|
58
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgenticCodingServer — abstract interface for interacting with an agentic
|
|
3
|
+
* coding backend. The application code depends only on this interface;
|
|
4
|
+
* concrete implementations (e.g. OpenCodeCodingServer) supply the wiring
|
|
5
|
+
* to a specific server.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgenticCodingServer, SessionMessage, AgentMode } from "./agentic-coding-server.js";
|
|
2
|
+
export declare class OpenCodeCodingServer implements AgenticCodingServer {
|
|
3
|
+
private client;
|
|
4
|
+
constructor(baseURL: string);
|
|
5
|
+
createSession(): Promise<string>;
|
|
6
|
+
deleteSession(sessionID: string): Promise<void>;
|
|
7
|
+
sendMessage(sessionID: string, text: string, agent?: string): Promise<void>;
|
|
8
|
+
getMessages(sessionID: string, limit?: number): Promise<SessionMessage[]>;
|
|
9
|
+
revertSession(sessionID: string, messageID: string): Promise<void>;
|
|
10
|
+
getAgentModes(): Promise<AgentMode[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concrete AgenticCodingServer backed by an OpenCode server
|
|
3
|
+
* via the @opencode-ai/sdk.
|
|
4
|
+
*/
|
|
5
|
+
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
6
|
+
import { cwd } from "process";
|
|
7
|
+
export class OpenCodeCodingServer {
|
|
8
|
+
client;
|
|
9
|
+
constructor(baseURL) {
|
|
10
|
+
this.client = createOpencodeClient({ baseUrl: baseURL, directory: cwd() });
|
|
11
|
+
}
|
|
12
|
+
async createSession() {
|
|
13
|
+
const { data: session } = await this.client.session.create();
|
|
14
|
+
if (!session)
|
|
15
|
+
throw new Error("OpenCode returned no session");
|
|
16
|
+
return session.id;
|
|
17
|
+
}
|
|
18
|
+
async deleteSession(sessionID) {
|
|
19
|
+
await this.client.session.delete({ sessionID });
|
|
20
|
+
}
|
|
21
|
+
async sendMessage(sessionID, text, agent) {
|
|
22
|
+
await this.client.session.promptAsync({
|
|
23
|
+
sessionID,
|
|
24
|
+
parts: [{ type: "text", text }],
|
|
25
|
+
...(agent ? { agent } : {}),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async getMessages(sessionID, limit) {
|
|
29
|
+
const { data: messages } = await this.client.session.messages({
|
|
30
|
+
sessionID,
|
|
31
|
+
...(limit != null ? { limit } : {}),
|
|
32
|
+
});
|
|
33
|
+
return (messages ?? []).map((m) => ({
|
|
34
|
+
id: m.info.id,
|
|
35
|
+
role: m.info.role,
|
|
36
|
+
parts: m.parts,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
async revertSession(sessionID, messageID) {
|
|
40
|
+
// Abort any in-progress generation first — swallow errors because the
|
|
41
|
+
// session may not be busy.
|
|
42
|
+
await this.client.session.abort({ sessionID }).catch(() => { });
|
|
43
|
+
await this.client.session.revert({ sessionID, messageID });
|
|
44
|
+
}
|
|
45
|
+
async getAgentModes() {
|
|
46
|
+
const { data: config } = await this.client.config.get();
|
|
47
|
+
const agent = config?.agent ?? {};
|
|
48
|
+
return Object.entries(agent)
|
|
49
|
+
.filter(([, val]) => val != null)
|
|
50
|
+
.map(([name, val]) => ({
|
|
51
|
+
name,
|
|
52
|
+
description: val.description ?? "",
|
|
53
|
+
model: val.model ?? "",
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/server/cron.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Sql } from "../db/index.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AgenticCodingServer } from "../lib/agentic-coding-server.js";
|
|
3
3
|
import type { CronJobRow } from "../db/index.js";
|
|
4
4
|
interface CronSchedulerOptions {
|
|
5
5
|
onJobExecuted?: (jobId: number, success: boolean, error?: string) => void;
|
|
@@ -8,10 +8,10 @@ export declare class CronScheduler {
|
|
|
8
8
|
private options;
|
|
9
9
|
private activeJobs;
|
|
10
10
|
private sql;
|
|
11
|
-
private
|
|
11
|
+
private agenticCodingServer;
|
|
12
12
|
private started;
|
|
13
13
|
constructor(options?: CronSchedulerOptions);
|
|
14
|
-
start(sql: Sql,
|
|
14
|
+
start(sql: Sql, agenticCodingServer: AgenticCodingServer): Promise<void>;
|
|
15
15
|
stop(): void;
|
|
16
16
|
private addJob;
|
|
17
17
|
private executeJob;
|
package/dist/server/cron.js
CHANGED
|
@@ -9,16 +9,16 @@ export class CronScheduler {
|
|
|
9
9
|
options;
|
|
10
10
|
activeJobs = new Map();
|
|
11
11
|
sql = null;
|
|
12
|
-
|
|
12
|
+
agenticCodingServer = null;
|
|
13
13
|
started = false;
|
|
14
14
|
constructor(options = {}) {
|
|
15
15
|
this.options = options;
|
|
16
16
|
}
|
|
17
|
-
async start(sql,
|
|
17
|
+
async start(sql, agenticCodingServer) {
|
|
18
18
|
if (this.started)
|
|
19
19
|
return;
|
|
20
20
|
this.sql = sql;
|
|
21
|
-
this.
|
|
21
|
+
this.agenticCodingServer = agenticCodingServer;
|
|
22
22
|
const rows = await sql `
|
|
23
23
|
SELECT id, name, schedule, timezone, message, session_name
|
|
24
24
|
FROM cron_jobs
|
|
@@ -39,7 +39,7 @@ export class CronScheduler {
|
|
|
39
39
|
console.log("Cron scheduler stopped");
|
|
40
40
|
}
|
|
41
41
|
addJob(job) {
|
|
42
|
-
if (!this.
|
|
42
|
+
if (!this.agenticCodingServer || !this.sql)
|
|
43
43
|
return;
|
|
44
44
|
const options = {
|
|
45
45
|
protect: true,
|
|
@@ -54,7 +54,7 @@ export class CronScheduler {
|
|
|
54
54
|
this.activeJobs.set(job.id, { cron, job });
|
|
55
55
|
}
|
|
56
56
|
async executeJob(job) {
|
|
57
|
-
if (!this.sql || !this.
|
|
57
|
+
if (!this.sql || !this.agenticCodingServer)
|
|
58
58
|
return;
|
|
59
59
|
const executedAt = new Date();
|
|
60
60
|
try {
|
|
@@ -65,11 +65,7 @@ export class CronScheduler {
|
|
|
65
65
|
throw new Error(`Session "${job.session_name}" not found`);
|
|
66
66
|
}
|
|
67
67
|
const injectText = `[Cron Job "${job.name}" — ${executedAt.toISOString()}]\n${job.message}${CRON_INJECTION_BLURB}`;
|
|
68
|
-
await this.
|
|
69
|
-
sessionID: session.session_id,
|
|
70
|
-
parts: [{ type: "text", text: injectText }],
|
|
71
|
-
...(session.agent ? { agent: session.agent } : {}),
|
|
72
|
-
});
|
|
68
|
+
await this.agenticCodingServer.sendMessage(session.session_id, injectText, session.agent ?? undefined);
|
|
73
69
|
await this.sql `
|
|
74
70
|
UPDATE cron_jobs SET last_run = ${executedAt} WHERE id = ${job.id}
|
|
75
71
|
`;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Sql } from "../db/index.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AgenticCodingServer } from "../lib/agentic-coding-server.js";
|
|
3
3
|
import { CronScheduler } from "./cron.js";
|
|
4
4
|
import type { MemoryManager } from "./memory.js";
|
|
5
|
-
export declare function createApp(sql: Sql,
|
|
5
|
+
export declare function createApp(sql: Sql, agenticCodingServer: AgenticCodingServer, password: string, serverUrl: string, cronScheduler: CronScheduler, memoryManager: MemoryManager): import("express-serve-static-core").Express;
|
package/dist/server/index.js
CHANGED
|
@@ -10,13 +10,13 @@ function authMiddleware(password) {
|
|
|
10
10
|
next();
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
export function createApp(sql,
|
|
13
|
+
export function createApp(sql, agenticCodingServer, password, serverUrl, cronScheduler, memoryManager) {
|
|
14
14
|
const app = express();
|
|
15
15
|
app.use(express.json());
|
|
16
16
|
// Worker routes are unauthenticated — mounted before auth middleware
|
|
17
|
-
app.use("/", createWorkerRouter(sql,
|
|
17
|
+
app.use("/", createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryManager));
|
|
18
18
|
// Everything else requires Bearer auth
|
|
19
19
|
app.use(authMiddleware(password));
|
|
20
|
-
app.use("/", createRouter(sql,
|
|
20
|
+
app.use("/", createRouter(sql, agenticCodingServer, serverUrl, cronScheduler, memoryManager));
|
|
21
21
|
return app;
|
|
22
22
|
}
|
package/dist/server/routes.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
2
|
import type { Sql } from "../db/index.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { AgenticCodingServer } from "../lib/agentic-coding-server.js";
|
|
4
4
|
import { CronScheduler } from "./cron.js";
|
|
5
5
|
import type { MemoryManager } from "./memory.js";
|
|
6
|
-
export declare function createRouter(sql: Sql,
|
|
7
|
-
export declare function createWorkerRouter(sql: Sql,
|
|
6
|
+
export declare function createRouter(sql: Sql, agenticCodingServer: AgenticCodingServer, serverUrl: string, scheduler: CronScheduler, memoryManager: MemoryManager): Router;
|
|
7
|
+
export declare function createWorkerRouter(sql: Sql, agenticCodingServer: AgenticCodingServer, serverUrl: string, memoryManager: MemoryManager): Router;
|
package/dist/server/routes.js
CHANGED
|
@@ -129,22 +129,14 @@ function generateWelcomeMessage(name, agent, status, humanName, humanDescription
|
|
|
129
129
|
``,
|
|
130
130
|
].join("\n");
|
|
131
131
|
}
|
|
132
|
-
export function createRouter(sql,
|
|
132
|
+
export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, memoryManager) {
|
|
133
133
|
const router = Router();
|
|
134
134
|
router.get("/health", (_req, res) => {
|
|
135
135
|
res.json({ ok: true });
|
|
136
136
|
});
|
|
137
137
|
router.get("/modes", async (_req, res) => {
|
|
138
138
|
try {
|
|
139
|
-
const
|
|
140
|
-
const agent = config?.agent ?? {};
|
|
141
|
-
const modes = Object.entries(agent)
|
|
142
|
-
.filter(([, val]) => val != null)
|
|
143
|
-
.map(([name, val]) => ({
|
|
144
|
-
name,
|
|
145
|
-
description: val.description ?? "",
|
|
146
|
-
model: val.model ?? "",
|
|
147
|
-
}));
|
|
139
|
+
const modes = await agenticCodingServer.getAgentModes();
|
|
148
140
|
res.json(modes);
|
|
149
141
|
}
|
|
150
142
|
catch (err) {
|
|
@@ -183,10 +175,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
183
175
|
}
|
|
184
176
|
let opencodeSessionId;
|
|
185
177
|
try {
|
|
186
|
-
|
|
187
|
-
if (!session)
|
|
188
|
-
throw new Error("No session returned");
|
|
189
|
-
opencodeSessionId = session.id;
|
|
178
|
+
opencodeSessionId = await agenticCodingServer.createSession();
|
|
190
179
|
}
|
|
191
180
|
catch (err) {
|
|
192
181
|
console.error("OpenCode session.create error:", err);
|
|
@@ -205,7 +194,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
205
194
|
catch (err) {
|
|
206
195
|
console.error("DB insert error:", err);
|
|
207
196
|
try {
|
|
208
|
-
await
|
|
197
|
+
await agenticCodingServer.deleteSession(opencodeSessionId);
|
|
209
198
|
}
|
|
210
199
|
catch { }
|
|
211
200
|
res.status(500).json({ error: "Internal server error" });
|
|
@@ -214,11 +203,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
214
203
|
try {
|
|
215
204
|
const clockInToken = `${row.agent_code}@${serverUrl}`;
|
|
216
205
|
const enrollmentMessage = generateEnrollmentMessage(clockInToken);
|
|
217
|
-
await
|
|
218
|
-
sessionID: opencodeSessionId,
|
|
219
|
-
parts: [{ type: "text", text: enrollmentMessage }],
|
|
220
|
-
...(trimmedAgent ? { agent: trimmedAgent } : {}),
|
|
221
|
-
});
|
|
206
|
+
await agenticCodingServer.sendMessage(opencodeSessionId, enrollmentMessage, trimmedAgent ?? undefined);
|
|
222
207
|
}
|
|
223
208
|
catch (err) {
|
|
224
209
|
console.warn("Warning: could not send first message to session:", err);
|
|
@@ -260,29 +245,28 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
260
245
|
}
|
|
261
246
|
const row = rows[0];
|
|
262
247
|
try {
|
|
263
|
-
const
|
|
264
|
-
const result =
|
|
248
|
+
const messages = await agenticCodingServer.getMessages(row.session_id, limit);
|
|
249
|
+
const result = messages
|
|
265
250
|
.slice(-limit)
|
|
266
251
|
.map((m) => ({
|
|
267
|
-
role: m.
|
|
252
|
+
role: m.role,
|
|
268
253
|
parts: m.parts.map((p) => {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return { type: "text", text: part.text ?? "" };
|
|
254
|
+
if (p.type === "text") {
|
|
255
|
+
return { type: "text", text: p.text ?? "" };
|
|
272
256
|
}
|
|
273
|
-
else if (
|
|
257
|
+
else if (p.type === "tool") {
|
|
274
258
|
// Extract tool name, input, and output from the tool state
|
|
275
|
-
const state =
|
|
259
|
+
const state = p.state;
|
|
276
260
|
return {
|
|
277
261
|
type: "tool",
|
|
278
|
-
tool:
|
|
262
|
+
tool: p.tool,
|
|
279
263
|
input: state?.input,
|
|
280
264
|
output: state?.output,
|
|
281
|
-
data:
|
|
265
|
+
data: p,
|
|
282
266
|
};
|
|
283
267
|
}
|
|
284
268
|
else {
|
|
285
|
-
return { type:
|
|
269
|
+
return { type: p.type, data: p };
|
|
286
270
|
}
|
|
287
271
|
}),
|
|
288
272
|
}))
|
|
@@ -310,10 +294,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
310
294
|
}
|
|
311
295
|
const row = rows[0];
|
|
312
296
|
try {
|
|
313
|
-
await
|
|
314
|
-
sessionID: row.session_id,
|
|
315
|
-
parts: [{ type: "text", text: text.trim() }],
|
|
316
|
-
});
|
|
297
|
+
await agenticCodingServer.sendMessage(row.session_id, text.trim());
|
|
317
298
|
res.json({ ok: true });
|
|
318
299
|
}
|
|
319
300
|
catch (err) {
|
|
@@ -332,28 +313,21 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
332
313
|
}
|
|
333
314
|
const session = rows[0];
|
|
334
315
|
try {
|
|
335
|
-
const
|
|
336
|
-
if (
|
|
316
|
+
const messages = await agenticCodingServer.getMessages(session.session_id);
|
|
317
|
+
if (messages.length === 0) {
|
|
337
318
|
res.status(400).json({ error: "Session has no messages to revert to" });
|
|
338
319
|
return;
|
|
339
320
|
}
|
|
340
321
|
const firstMessage = messages[0];
|
|
341
|
-
if (!firstMessage
|
|
322
|
+
if (!firstMessage.id) {
|
|
342
323
|
res.status(500).json({ error: "Failed to get first message ID" });
|
|
343
324
|
return;
|
|
344
325
|
}
|
|
345
|
-
|
|
346
|
-
// Swallow errors — the session may not be busy, in which case abort is a no-op.
|
|
347
|
-
await opencode.session.abort({ sessionID: session.session_id }).catch(() => { });
|
|
348
|
-
await opencode.session.revert({ sessionID: session.session_id, messageID: firstMessage.info.id });
|
|
326
|
+
await agenticCodingServer.revertSession(session.session_id, firstMessage.id);
|
|
349
327
|
const clockInToken = `${session.agent_code}@${serverUrl}`;
|
|
350
328
|
const enrollmentMessage = generateEnrollmentMessage(clockInToken);
|
|
351
|
-
await
|
|
352
|
-
|
|
353
|
-
parts: [{ type: "text", text: enrollmentMessage }],
|
|
354
|
-
...(session.agent ? { agent: session.agent } : {}),
|
|
355
|
-
});
|
|
356
|
-
res.json({ ok: true, messageID: firstMessage.info.id });
|
|
329
|
+
await agenticCodingServer.sendMessage(session.session_id, enrollmentMessage, session.agent ?? undefined);
|
|
330
|
+
res.json({ ok: true, messageID: firstMessage.id });
|
|
357
331
|
}
|
|
358
332
|
catch (err) {
|
|
359
333
|
console.error("POST /sessions/:name/revert-to-start error:", err);
|
|
@@ -367,26 +341,20 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
367
341
|
const results = [];
|
|
368
342
|
for (const session of allSessions) {
|
|
369
343
|
try {
|
|
370
|
-
const
|
|
371
|
-
if (
|
|
344
|
+
const messages = await agenticCodingServer.getMessages(session.session_id);
|
|
345
|
+
if (messages.length === 0) {
|
|
372
346
|
results.push({ name: session.name, ok: false, error: "No messages" });
|
|
373
347
|
continue;
|
|
374
348
|
}
|
|
375
349
|
const firstMessage = messages[0];
|
|
376
|
-
if (!firstMessage
|
|
350
|
+
if (!firstMessage.id) {
|
|
377
351
|
results.push({ name: session.name, ok: false, error: "Failed to get first message ID" });
|
|
378
352
|
continue;
|
|
379
353
|
}
|
|
380
|
-
|
|
381
|
-
await opencode.session.abort({ sessionID: session.session_id }).catch(() => { });
|
|
382
|
-
await opencode.session.revert({ sessionID: session.session_id, messageID: firstMessage.info.id });
|
|
354
|
+
await agenticCodingServer.revertSession(session.session_id, firstMessage.id);
|
|
383
355
|
const clockInToken = `${session.agent_code}@${serverUrl}`;
|
|
384
356
|
const enrollmentMessage = generateEnrollmentMessage(clockInToken);
|
|
385
|
-
await
|
|
386
|
-
sessionID: session.session_id,
|
|
387
|
-
parts: [{ type: "text", text: enrollmentMessage }],
|
|
388
|
-
...(session.agent ? { agent: session.agent } : {}),
|
|
389
|
-
});
|
|
357
|
+
await agenticCodingServer.sendMessage(session.session_id, enrollmentMessage, session.agent ?? undefined);
|
|
390
358
|
results.push({ name: session.name, ok: true });
|
|
391
359
|
}
|
|
392
360
|
catch (err) {
|
|
@@ -408,7 +376,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
408
376
|
}
|
|
409
377
|
const row = rows[0];
|
|
410
378
|
try {
|
|
411
|
-
await
|
|
379
|
+
await agenticCodingServer.deleteSession(row.session_id);
|
|
412
380
|
}
|
|
413
381
|
catch (err) {
|
|
414
382
|
console.error("OpenCode session.delete error:", err);
|
|
@@ -544,10 +512,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
544
512
|
const sessionId = sessionMap.get(recipient);
|
|
545
513
|
const injectText = `[Message from "${trimmedFrom}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
|
|
546
514
|
try {
|
|
547
|
-
await
|
|
548
|
-
sessionID: sessionId,
|
|
549
|
-
parts: [{ type: "text", text: injectText }],
|
|
550
|
-
});
|
|
515
|
+
await agenticCodingServer.sendMessage(sessionId, injectText);
|
|
551
516
|
await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
|
|
552
517
|
injected = true;
|
|
553
518
|
}
|
|
@@ -956,7 +921,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
|
|
|
956
921
|
});
|
|
957
922
|
return router;
|
|
958
923
|
}
|
|
959
|
-
export function createWorkerRouter(sql,
|
|
924
|
+
export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryManager) {
|
|
960
925
|
const router = Router();
|
|
961
926
|
router.get("/worker/clock-in", async (req, res) => {
|
|
962
927
|
const { code } = req.query;
|
|
@@ -1108,10 +1073,7 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
|
|
|
1108
1073
|
const recipientSessionId = sessionMap.get(recipient);
|
|
1109
1074
|
const injectText = `[Message from "${session.name}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
|
|
1110
1075
|
try {
|
|
1111
|
-
await
|
|
1112
|
-
sessionID: recipientSessionId,
|
|
1113
|
-
parts: [{ type: "text", text: injectText }],
|
|
1114
|
-
});
|
|
1076
|
+
await agenticCodingServer.sendMessage(recipientSessionId, injectText);
|
|
1115
1077
|
await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
|
|
1116
1078
|
injected = true;
|
|
1117
1079
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-office",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.13",
|
|
4
|
+
"description": "An office for your AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Richard Anaya",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@inkjs/ui": "^2.0.0",
|
|
36
36
|
"@opencode-ai/sdk": "^1.2.10",
|
|
37
|
+
"agent-office": "^0.0.12",
|
|
37
38
|
"commander": "^14.0.0",
|
|
38
39
|
"croner": "^10.0.1",
|
|
39
40
|
"dotenv": "^17.0.0",
|
package/dist/lib/opencode.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { createOpencodeClient as _createOpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
2
|
-
export type OpencodeClient = ReturnType<typeof _createOpencodeClient>;
|
|
3
|
-
export declare function createOpencodeClient(baseURL: string): OpencodeClient;
|
|
4
|
-
export interface OpencodeSession {
|
|
5
|
-
id: string;
|
|
6
|
-
title?: string;
|
|
7
|
-
}
|
package/dist/lib/opencode.js
DELETED