agent-office 0.4.6 → 0.4.8
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 +46 -22
- package/dist/cli.js +101 -4
- package/dist/commands/communicator.d.ts +1 -0
- package/dist/commands/communicator.js +1385 -2
- package/dist/commands/task-board.d.ts +29 -0
- package/dist/commands/task-board.js +251 -0
- package/dist/commands/worker.d.ts +2 -1
- package/dist/commands/worker.js +7 -3
- package/dist/db/index.d.ts +23 -0
- package/dist/db/postgresql-storage.d.ts +17 -1
- package/dist/db/postgresql-storage.js +175 -0
- package/dist/db/sqlite-storage.d.ts +17 -1
- package/dist/db/sqlite-storage.js +241 -0
- package/dist/db/storage-base.d.ts +17 -1
- package/dist/db/storage.d.ts +17 -1
- package/dist/manage/app.js +6 -1
- package/dist/manage/components/CronRequests.d.ts +8 -0
- package/dist/manage/components/CronRequests.js +181 -0
- package/dist/manage/hooks/useApi.d.ts +22 -0
- package/dist/manage/hooks/useApi.js +18 -0
- package/dist/server/routes.js +184 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agent-office
|
|
2
2
|
|
|
3
|
-
An office for your AI agents. Manage multiple [OpenCode](https://opencode.ai) coding sessions as named coworkers with inter-agent messaging, scheduled tasks
|
|
3
|
+
An office for your AI agents. Manage multiple [OpenCode](https://opencode.ai) coding sessions as named coworkers with inter-agent messaging, **human-approved scheduled tasks**, and a terminal UI for human oversight.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -8,13 +8,13 @@ An office for your AI agents. Manage multiple [OpenCode](https://opencode.ai) co
|
|
|
8
8
|
|
|
9
9
|
**For humans**, there are three interfaces:
|
|
10
10
|
|
|
11
|
-
- **`serve`** -- an HTTP server that manages sessions, messages, and cron
|
|
12
|
-
- **`manage`** -- a full-screen terminal UI (React Ink) for creating coworkers, sending messages, browsing mail,
|
|
11
|
+
- **`serve`** -- an HTTP server that manages sessions, messages, and **cron job approval workflow**, backed by PostgreSQL and a running OpenCode server
|
|
12
|
+
- **`manage`** -- a full-screen terminal UI (React Ink) for creating coworkers, sending messages, browsing mail, **approving/rejecting cron requests**, and observing agent activity
|
|
13
13
|
- **`communicator web`** -- a browser-based chat interface for conversing with a specific agent in real time
|
|
14
14
|
|
|
15
15
|
**For AI agents**, there is a CLI:
|
|
16
16
|
|
|
17
|
-
- **`worker`** -- subcommands that agents invoke from within their OpenCode sessions to clock in, message coworkers, set status, and
|
|
17
|
+
- **`worker`** -- subcommands that agents invoke from within their OpenCode sessions to clock in, message coworkers, set status, and **request cron jobs** (which require human approval)
|
|
18
18
|
|
|
19
19
|
```
|
|
20
20
|
+---------------------+
|
|
@@ -97,7 +97,7 @@ Opens a web-based chat interface at `http://127.0.0.1:7655` for real-time conver
|
|
|
97
97
|
|
|
98
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
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 and
|
|
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 and **request cron jobs** (which require human approval).
|
|
101
101
|
|
|
102
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
103
|
|
|
@@ -113,6 +113,22 @@ Agent sessions are private by design. The welcome message tells each agent:
|
|
|
113
113
|
|
|
114
114
|
This means agents must actively communicate to report progress, ask questions, or share results.
|
|
115
115
|
|
|
116
|
+
### Cron Job Approval Workflow
|
|
117
|
+
|
|
118
|
+
To maintain human oversight over automated tasks, cron jobs require approval before becoming active:
|
|
119
|
+
|
|
120
|
+
1. **Request** -- AI agents use `agent-office worker cron request` to submit a cron job request with name, schedule, message, and timezone
|
|
121
|
+
2. **Review** -- Human managers see pending requests in the TUI under "Cron requests" or via API
|
|
122
|
+
3. **Approve/Reject** -- Managers can approve requests (creates active cron job) or reject with notes
|
|
123
|
+
4. **Notification** -- Workers receive messages about approval/rejection with manager feedback
|
|
124
|
+
5. **Active Jobs** -- Approved cron jobs appear in `agent-office worker cron list` and execute according to schedule
|
|
125
|
+
|
|
126
|
+
This workflow ensures:
|
|
127
|
+
- No automated tasks run without human review
|
|
128
|
+
- Managers can provide feedback on scheduling decisions
|
|
129
|
+
- Clear audit trail of all cron job requests and decisions
|
|
130
|
+
- Workers understand the approval process and can resubmit if needed
|
|
131
|
+
|
|
116
132
|
### Authentication
|
|
117
133
|
|
|
118
134
|
Two authentication schemes run in parallel:
|
|
@@ -168,6 +184,7 @@ Options:
|
|
|
168
184
|
| **Send message** | Select a recipient and compose a message |
|
|
169
185
|
| **My mail** | View received and sent messages. `r` reply, `m` mark read, `a` mark all read. Tab between received/sent |
|
|
170
186
|
| **Cron jobs** | Table of scheduled tasks with name, coworker, schedule, next run, and status. Create, delete, enable/disable, view history |
|
|
187
|
+
| **Cron requests** | Approve or reject pending cron job requests from workers. View request details, approve with notes, reject with reason |
|
|
171
188
|
| **My profile** | Set your display name and description (visible to agents in their welcome message) |
|
|
172
189
|
|
|
173
190
|
A sidebar shows all coworkers with live status indicators, refreshed every 5 seconds. An unread mail badge appears in the header.
|
|
@@ -185,9 +202,10 @@ Options:
|
|
|
185
202
|
--password <password> API password (env: AGENT_OFFICE_PASSWORD)
|
|
186
203
|
--host <host> Communicator bind host (default: 127.0.0.1)
|
|
187
204
|
--port <port> Communicator bind port (default: 7655)
|
|
205
|
+
--xai-key <key> xAI API key for voice chat (enables voice button)
|
|
188
206
|
```
|
|
189
207
|
|
|
190
|
-
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.
|
|
208
|
+
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. **Voice mode**: When an xAI API key is provided, a microphone button appears for voice conversations with full tool access (read/write/edit/bash).
|
|
191
209
|
|
|
192
210
|
### `agent-office worker` (for AI agents)
|
|
193
211
|
|
|
@@ -213,14 +231,15 @@ agent-office worker send-message --name Alice --body "Cost is \$50" <token>
|
|
|
213
231
|
# > Warning: always escape $ in --body when using double quotes in bash, or use single quotes.
|
|
214
232
|
# > Unescaped $ will be silently expanded by the shell before the message is sent.
|
|
215
233
|
|
|
216
|
-
# Cron job management
|
|
217
|
-
agent-office worker cron list <token>
|
|
218
|
-
agent-office worker cron
|
|
219
|
-
agent-office worker cron
|
|
220
|
-
agent-office worker cron
|
|
221
|
-
agent-office worker cron
|
|
222
|
-
agent-office worker cron
|
|
223
|
-
agent-office worker cron
|
|
234
|
+
# Cron job management (requires human approval)
|
|
235
|
+
agent-office worker cron list <token> # View your approved cron jobs
|
|
236
|
+
agent-office worker cron request --name "daily-report" --schedule "0 9 * * *" --message "Send daily status" --respond-to "Manager" <token>
|
|
237
|
+
agent-office worker cron request --name "weekly" --schedule "0 9 * * 1" --message "Weekly sync" --timezone "America/New_York" --respond-to "Manager" <token>
|
|
238
|
+
agent-office worker cron requests <token> # View status of all your requests (pending/approved/rejected)
|
|
239
|
+
agent-office worker cron delete <token> <id> # Delete your approved cron jobs
|
|
240
|
+
agent-office worker cron enable <token> <id> # Enable your approved cron jobs
|
|
241
|
+
agent-office worker cron disable <token> <id> # Disable your approved cron jobs
|
|
242
|
+
agent-office worker cron history <token> <id> # View execution history
|
|
224
243
|
|
|
225
244
|
```
|
|
226
245
|
|
|
@@ -245,12 +264,15 @@ agent-office worker cron history <token> <id>
|
|
|
245
264
|
| `GET` | `/messages/:name` | Get messages for a person. Query: `?sent=true`, `?unread_only=true` |
|
|
246
265
|
| `POST` | `/messages` | Send a message. Body: `{ from, to: string[], body }` |
|
|
247
266
|
| `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? }` |
|
|
267
|
+
| `GET` | `/crons` | List approved cron jobs. Query: `?session_name=<name>` |
|
|
268
|
+
| `POST` | `/crons` | Create a cron job (admin only). Body: `{ name, session_name, schedule, message, timezone? }` |
|
|
250
269
|
| `DELETE` | `/crons/:id` | Delete a cron job |
|
|
251
270
|
| `POST` | `/crons/:id/enable` | Enable a cron job |
|
|
252
271
|
| `POST` | `/crons/:id/disable` | Disable a cron job |
|
|
253
272
|
| `GET` | `/crons/:id/history` | Cron execution history. Query: `?limit=N` |
|
|
273
|
+
| `GET` | `/cron-requests` | List all cron requests. Query: `?status=<status>&session_name=<name>` |
|
|
274
|
+
| `POST` | `/cron-requests/:id/approve` | Approve a cron request. Body: `{ notes? }` |
|
|
275
|
+
| `POST` | `/cron-requests/:id/reject` | Reject a cron request. Body: `{ notes? }` |
|
|
254
276
|
|
|
255
277
|
|
|
256
278
|
### Worker Endpoints (agent code auth via `?code=<uuid>`)
|
|
@@ -261,11 +283,12 @@ agent-office worker cron history <token> <id>
|
|
|
261
283
|
| `GET` | `/worker/list-coworkers` | List all other agents and the human manager |
|
|
262
284
|
| `POST` | `/worker/set-status` | Set or clear status. Body: `{ status }` |
|
|
263
285
|
| `POST` | `/worker/send-message` | Send a message. Body: `{ to: string[], body }` |
|
|
264
|
-
| `GET` | `/worker/crons` | List own cron jobs |
|
|
265
|
-
| `
|
|
266
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `POST` | `/worker/crons/:id/
|
|
286
|
+
| `GET` | `/worker/crons` | List own approved cron jobs |
|
|
287
|
+
| `GET` | `/worker/cron-requests` | List own cron job requests (pending/approved/rejected) |
|
|
288
|
+
| `POST` | `/worker/cron-requests` | Request a new cron job (requires human approval) |
|
|
289
|
+
| `DELETE` | `/worker/crons/:id` | Delete own approved cron job |
|
|
290
|
+
| `POST` | `/worker/crons/:id/enable` | Enable own approved cron job |
|
|
291
|
+
| `POST` | `/worker/crons/:id/disable` | Disable own approved cron job |
|
|
269
292
|
| `GET` | `/worker/crons/:id/history` | View own cron job history |
|
|
270
293
|
|
|
271
294
|
|
|
@@ -278,7 +301,8 @@ The server uses PostgreSQL with automatic migrations. Tables:
|
|
|
278
301
|
- **`sessions`** -- Maps coworker names to OpenCode session IDs with agent codes, agent modes, and status
|
|
279
302
|
- **`config`** -- Key-value store for application settings (`human_name`, `human_description`)
|
|
280
303
|
- **`messages`** -- Inter-agent and human-agent mail with read/injected tracking
|
|
281
|
-
- **`
|
|
304
|
+
- **`cron_requests`** -- Pending cron job requests requiring human approval with status, reviewer notes, and timestamps
|
|
305
|
+
- **`cron_jobs`** -- Approved scheduled tasks tied to sessions with cron expressions and timezone support
|
|
282
306
|
- **`cron_history`** -- Execution log for cron jobs with success/failure tracking
|
|
283
307
|
|
|
284
308
|
### Agentic Coding Server Abstraction
|
package/dist/cli.js
CHANGED
|
@@ -22,6 +22,94 @@ program
|
|
|
22
22
|
const { serve } = await import("./commands/serve.js");
|
|
23
23
|
await serve(options);
|
|
24
24
|
});
|
|
25
|
+
const taskBoardCmd = program
|
|
26
|
+
.command("task-board")
|
|
27
|
+
.description("Manage a kanban-style task board with columns for task lifecycle tracking. Supports full CRUD operations, search, and analytics. Requires database connection (--database-url or --sqlite).")
|
|
28
|
+
.option("--database-url <url>", "PostgreSQL database connection string for storing task data", process.env.DATABASE_URL)
|
|
29
|
+
.option("--sqlite <path>", "Path to SQLite database file for storing task data (alternative to PostgreSQL)", process.env.AGENT_OFFICE_SQLITE);
|
|
30
|
+
taskBoardCmd
|
|
31
|
+
.command("list")
|
|
32
|
+
.description("Display all tasks on the board, ordered by creation date (newest first). Use --column to filter tasks by their current column status.")
|
|
33
|
+
.option("--column <column>", "Filter tasks to show only those in the specified column. Valid columns: idea, approved idea, working on, blocked, ready for review, done")
|
|
34
|
+
.action(async (cmdOptions) => {
|
|
35
|
+
const { listTasks } = await import("./commands/task-board.js");
|
|
36
|
+
const options = taskBoardCmd.opts();
|
|
37
|
+
await listTasks(options, cmdOptions);
|
|
38
|
+
});
|
|
39
|
+
taskBoardCmd
|
|
40
|
+
.command("add <title> <description>")
|
|
41
|
+
.description("Create a new task on the board. Title and description are required. Task starts in 'idea' column by default.")
|
|
42
|
+
.option("--assignee <assignee>", "Assign the task to a specific person or team member")
|
|
43
|
+
.option("--column <column>", "Place the task in a specific column (default: idea). Valid columns: idea, approved idea, working on, blocked, ready for review, done")
|
|
44
|
+
.option("--dependencies <deps>", "Specify task IDs this new task depends on, as a comma-separated list (e.g., '1,3,5')")
|
|
45
|
+
.action(async (title, description, cmdOptions) => {
|
|
46
|
+
const { addTask } = await import("./commands/task-board.js");
|
|
47
|
+
const options = taskBoardCmd.opts();
|
|
48
|
+
await addTask(options, title, description, cmdOptions);
|
|
49
|
+
});
|
|
50
|
+
taskBoardCmd
|
|
51
|
+
.command("update <id>")
|
|
52
|
+
.description("Modify an existing task by its ID. You can update any combination of title, description, assignee, column, or dependencies. Only specified fields will be changed.")
|
|
53
|
+
.option("--title <title>", "Update the task's title")
|
|
54
|
+
.option("--description <desc>", "Update the task's description")
|
|
55
|
+
.option("--assignee <assignee>", "Change or set the task assignee")
|
|
56
|
+
.option("--column <column>", "Move the task to a different column. Valid columns: idea, approved idea, working on, blocked, ready for review, done")
|
|
57
|
+
.option("--dependencies <deps>", "Update the task dependencies as a comma-separated list of task IDs (e.g., '2,4')")
|
|
58
|
+
.action(async (id, cmdOptions) => {
|
|
59
|
+
const { updateTask } = await import("./commands/task-board.js");
|
|
60
|
+
const options = taskBoardCmd.opts();
|
|
61
|
+
await updateTask(options, id, cmdOptions);
|
|
62
|
+
});
|
|
63
|
+
taskBoardCmd
|
|
64
|
+
.command("delete <id>")
|
|
65
|
+
.description("Permanently remove a task from the board by its ID. This action cannot be undone.")
|
|
66
|
+
.action(async (id) => {
|
|
67
|
+
const { deleteTask } = await import("./commands/task-board.js");
|
|
68
|
+
const options = taskBoardCmd.opts();
|
|
69
|
+
await deleteTask(options, id);
|
|
70
|
+
});
|
|
71
|
+
taskBoardCmd
|
|
72
|
+
.command("move <id> <column>")
|
|
73
|
+
.description("Change a task's status by moving it to a different column on the board. This is a quick way to update task progress.")
|
|
74
|
+
.action(async (id, column) => {
|
|
75
|
+
const { moveTask } = await import("./commands/task-board.js");
|
|
76
|
+
const options = taskBoardCmd.opts();
|
|
77
|
+
await moveTask(options, id, column);
|
|
78
|
+
});
|
|
79
|
+
taskBoardCmd
|
|
80
|
+
.command("search <query>")
|
|
81
|
+
.description("Find tasks that contain the query string at the beginning of their title or description (case-insensitive prefix search). Combine with filters for more precise results.")
|
|
82
|
+
.option("--assignee <assignee>", "Only show tasks assigned to the specified person")
|
|
83
|
+
.option("--column <column>", "Only show tasks in the specified column. Valid columns: idea, approved idea, working on, blocked, ready for review, done")
|
|
84
|
+
.action(async (query, cmdOptions) => {
|
|
85
|
+
const { searchTasks } = await import("./commands/task-board.js");
|
|
86
|
+
const options = taskBoardCmd.opts();
|
|
87
|
+
await searchTasks(options, query, cmdOptions);
|
|
88
|
+
});
|
|
89
|
+
taskBoardCmd
|
|
90
|
+
.command("assign <id> <assignee>")
|
|
91
|
+
.description("Assign an existing task to a specific person or team member. This updates only the assignee field without modifying other task properties.")
|
|
92
|
+
.action(async (id, assignee) => {
|
|
93
|
+
const { assignTask } = await import("./commands/task-board.js");
|
|
94
|
+
const options = taskBoardCmd.opts();
|
|
95
|
+
await assignTask(options, id, assignee);
|
|
96
|
+
});
|
|
97
|
+
taskBoardCmd
|
|
98
|
+
.command("show <id>")
|
|
99
|
+
.description("Display complete details for a specific task, including all fields, timestamps, and dependency information. Useful for getting full context about a task.")
|
|
100
|
+
.action(async (id) => {
|
|
101
|
+
const { showTask } = await import("./commands/task-board.js");
|
|
102
|
+
const options = taskBoardCmd.opts();
|
|
103
|
+
await showTask(options, id);
|
|
104
|
+
});
|
|
105
|
+
taskBoardCmd
|
|
106
|
+
.command("stats")
|
|
107
|
+
.description("Show comprehensive statistics about the entire task board, including total task count, distribution across columns, and assignment breakdown.")
|
|
108
|
+
.action(async () => {
|
|
109
|
+
const { showStats } = await import("./commands/task-board.js");
|
|
110
|
+
const options = taskBoardCmd.opts();
|
|
111
|
+
await showStats(options);
|
|
112
|
+
});
|
|
25
113
|
program
|
|
26
114
|
.command("manage")
|
|
27
115
|
.description("[HUMAN ONLY] Launch the interactive TUI to manage sessions")
|
|
@@ -41,6 +129,7 @@ appCmd
|
|
|
41
129
|
.option("--password <password>", "API password for the agent-office server", process.env.AGENT_OFFICE_PASSWORD ?? "secret")
|
|
42
130
|
.option("--host <host>", "Host to bind the web server to", "127.0.0.1")
|
|
43
131
|
.option("--port <port>", "Port to run the web server on", "7655")
|
|
132
|
+
.option("--xai-key <key>", "xAI API key for voice chat (enables voice button)", process.env.XAI_API_KEY)
|
|
44
133
|
.action(async (options) => {
|
|
45
134
|
const { appCoworkerChatWeb } = await import("./commands/communicator.js");
|
|
46
135
|
await appCoworkerChatWeb(options);
|
|
@@ -139,9 +228,9 @@ cronCmd
|
|
|
139
228
|
await listCrons(token);
|
|
140
229
|
});
|
|
141
230
|
cronCmd
|
|
142
|
-
.command("
|
|
231
|
+
.command("request")
|
|
143
232
|
.argument("<token>", "Agent token in the format <agent_code>@<server-url>")
|
|
144
|
-
.description("
|
|
233
|
+
.description("Request a new cron job (requires human approval)")
|
|
145
234
|
.requiredOption("--name <name>", "Cron job name")
|
|
146
235
|
.requiredOption("--schedule <schedule>", "Cron expression (e.g., '0 9 * * *' for daily at 9am)")
|
|
147
236
|
.requiredOption("--message <message>", "Action to perform when job fires")
|
|
@@ -157,8 +246,16 @@ Warning:
|
|
|
157
246
|
The safest option is always single quotes: --message 'your message here'
|
|
158
247
|
(single quotes prevent all shell interpretation)`)
|
|
159
248
|
.action(async (token, options) => {
|
|
160
|
-
const {
|
|
161
|
-
await
|
|
249
|
+
const { requestCron } = await import("./commands/worker.js");
|
|
250
|
+
await requestCron(token, options);
|
|
251
|
+
});
|
|
252
|
+
cronCmd
|
|
253
|
+
.command("requests")
|
|
254
|
+
.argument("<token>", "Agent token in the format <agent_code>@<server-url>")
|
|
255
|
+
.description("List your cron job requests and their status")
|
|
256
|
+
.action(async (token) => {
|
|
257
|
+
const { listCronRequests } = await import("./commands/worker.js");
|
|
258
|
+
await listCronRequests(token);
|
|
162
259
|
});
|
|
163
260
|
cronCmd
|
|
164
261
|
.command("delete")
|