agent-office 0.4.5 → 0.4.7

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 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, and a terminal UI for human oversight.
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 jobs, 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
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 manage cron jobs
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
  +---------------------+
@@ -40,6 +40,10 @@ An office for your AI agents. Manage multiple [OpenCode](https://opencode.ai) co
40
40
  +-----------------------+
41
41
  ```
42
42
 
43
+ ## Breaking Changes in v0.4.7
44
+
45
+ **Cron Job Approval Workflow**: Workers can no longer create cron jobs directly. Instead, they must use `agent-office worker cron request` to submit requests that require human approval. The old `agent-office worker cron create` command has been renamed to `cron request`. This change ensures all automated tasks have human oversight.
46
+
43
47
  ## Installation
44
48
 
45
49
  ```bash
@@ -97,7 +101,7 @@ Opens a web-based chat interface at `http://127.0.0.1:7655` for real-time conver
97
101
 
98
102
  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
103
 
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 create cron jobs.
104
+ 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
105
 
102
106
  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
107
 
@@ -113,6 +117,22 @@ Agent sessions are private by design. The welcome message tells each agent:
113
117
 
114
118
  This means agents must actively communicate to report progress, ask questions, or share results.
115
119
 
120
+ ### Cron Job Approval Workflow
121
+
122
+ To maintain human oversight over automated tasks, cron jobs require approval before becoming active:
123
+
124
+ 1. **Request** -- AI agents use `agent-office worker cron request` to submit a cron job request with name, schedule, message, and timezone
125
+ 2. **Review** -- Human managers see pending requests in the TUI under "Cron requests" or via API
126
+ 3. **Approve/Reject** -- Managers can approve requests (creates active cron job) or reject with notes
127
+ 4. **Notification** -- Workers receive messages about approval/rejection with manager feedback
128
+ 5. **Active Jobs** -- Approved cron jobs appear in `agent-office worker cron list` and execute according to schedule
129
+
130
+ This workflow ensures:
131
+ - No automated tasks run without human review
132
+ - Managers can provide feedback on scheduling decisions
133
+ - Clear audit trail of all cron job requests and decisions
134
+ - Workers understand the approval process and can resubmit if needed
135
+
116
136
  ### Authentication
117
137
 
118
138
  Two authentication schemes run in parallel:
@@ -168,6 +188,7 @@ Options:
168
188
  | **Send message** | Select a recipient and compose a message |
169
189
  | **My mail** | View received and sent messages. `r` reply, `m` mark read, `a` mark all read. Tab between received/sent |
170
190
  | **Cron jobs** | Table of scheduled tasks with name, coworker, schedule, next run, and status. Create, delete, enable/disable, view history |
191
+ | **Cron requests** | Approve or reject pending cron job requests from workers. View request details, approve with notes, reject with reason |
171
192
  | **My profile** | Set your display name and description (visible to agents in their welcome message) |
172
193
 
173
194
  A sidebar shows all coworkers with live status indicators, refreshed every 5 seconds. An unread mail badge appears in the header.
@@ -213,14 +234,15 @@ agent-office worker send-message --name Alice --body "Cost is \$50" <token>
213
234
  # > Warning: always escape $ in --body when using double quotes in bash, or use single quotes.
214
235
  # > Unescaped $ will be silently expanded by the shell before the message is sent.
215
236
 
216
- # Cron job management
217
- agent-office worker cron list <token>
218
- agent-office worker cron create --name "daily-report" --schedule "0 9 * * *" --message "Send daily status" <token>
219
- agent-office worker cron create --name "weekly" --schedule "0 9 * * 1" --message "Weekly sync" --timezone "America/New_York" <token>
220
- agent-office worker cron delete <token> <id>
221
- agent-office worker cron enable <token> <id>
222
- agent-office worker cron disable <token> <id>
223
- agent-office worker cron history <token> <id>
237
+ # Cron job management (requires human approval)
238
+ agent-office worker cron list <token> # View your approved cron jobs
239
+ agent-office worker cron request --name "daily-report" --schedule "0 9 * * *" --message "Send daily status" --respond-to "Manager" <token>
240
+ agent-office worker cron request --name "weekly" --schedule "0 9 * * 1" --message "Weekly sync" --timezone "America/New_York" --respond-to "Manager" <token>
241
+ agent-office worker cron requests <token> # View status of all your requests (pending/approved/rejected)
242
+ agent-office worker cron delete <token> <id> # Delete your approved cron jobs
243
+ agent-office worker cron enable <token> <id> # Enable your approved cron jobs
244
+ agent-office worker cron disable <token> <id> # Disable your approved cron jobs
245
+ agent-office worker cron history <token> <id> # View execution history
224
246
 
225
247
  ```
226
248
 
@@ -245,12 +267,15 @@ agent-office worker cron history <token> <id>
245
267
  | `GET` | `/messages/:name` | Get messages for a person. Query: `?sent=true`, `?unread_only=true` |
246
268
  | `POST` | `/messages` | Send a message. Body: `{ from, to: string[], body }` |
247
269
  | `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? }` |
270
+ | `GET` | `/crons` | List approved cron jobs. Query: `?session_name=<name>` |
271
+ | `POST` | `/crons` | Create a cron job (admin only). Body: `{ name, session_name, schedule, message, timezone? }` |
250
272
  | `DELETE` | `/crons/:id` | Delete a cron job |
251
273
  | `POST` | `/crons/:id/enable` | Enable a cron job |
252
274
  | `POST` | `/crons/:id/disable` | Disable a cron job |
253
275
  | `GET` | `/crons/:id/history` | Cron execution history. Query: `?limit=N` |
276
+ | `GET` | `/cron-requests` | List all cron requests. Query: `?status=<status>&session_name=<name>` |
277
+ | `POST` | `/cron-requests/:id/approve` | Approve a cron request. Body: `{ notes? }` |
278
+ | `POST` | `/cron-requests/:id/reject` | Reject a cron request. Body: `{ notes? }` |
254
279
 
255
280
 
256
281
  ### Worker Endpoints (agent code auth via `?code=<uuid>`)
@@ -261,11 +286,12 @@ agent-office worker cron history <token> <id>
261
286
  | `GET` | `/worker/list-coworkers` | List all other agents and the human manager |
262
287
  | `POST` | `/worker/set-status` | Set or clear status. Body: `{ status }` |
263
288
  | `POST` | `/worker/send-message` | Send a message. Body: `{ to: string[], body }` |
264
- | `GET` | `/worker/crons` | List own cron jobs |
265
- | `POST` | `/worker/crons` | Create a cron job |
266
- | `DELETE` | `/worker/crons/:id` | Delete own cron job |
267
- | `POST` | `/worker/crons/:id/enable` | Enable own cron job |
268
- | `POST` | `/worker/crons/:id/disable` | Disable own cron job |
289
+ | `GET` | `/worker/crons` | List own approved cron jobs |
290
+ | `GET` | `/worker/cron-requests` | List own cron job requests (pending/approved/rejected) |
291
+ | `POST` | `/worker/cron-requests` | Request a new cron job (requires human approval) |
292
+ | `DELETE` | `/worker/crons/:id` | Delete own approved cron job |
293
+ | `POST` | `/worker/crons/:id/enable` | Enable own approved cron job |
294
+ | `POST` | `/worker/crons/:id/disable` | Disable own approved cron job |
269
295
  | `GET` | `/worker/crons/:id/history` | View own cron job history |
270
296
 
271
297
 
@@ -278,7 +304,8 @@ The server uses PostgreSQL with automatic migrations. Tables:
278
304
  - **`sessions`** -- Maps coworker names to OpenCode session IDs with agent codes, agent modes, and status
279
305
  - **`config`** -- Key-value store for application settings (`human_name`, `human_description`)
280
306
  - **`messages`** -- Inter-agent and human-agent mail with read/injected tracking
281
- - **`cron_jobs`** -- Scheduled tasks tied to sessions with cron expressions and timezone support
307
+ - **`cron_requests`** -- Pending cron job requests requiring human approval with status, reviewer notes, and timestamps
308
+ - **`cron_jobs`** -- Approved scheduled tasks tied to sessions with cron expressions and timezone support
282
309
  - **`cron_history`** -- Execution log for cron jobs with success/failure tracking
283
310
 
284
311
  ### Agentic Coding Server Abstraction
package/dist/cli.js CHANGED
@@ -15,10 +15,101 @@ program
15
15
  .option("--port <port>", "Port to serve on", "7654")
16
16
  .option("--password <password>", "REQUIRED. API password", process.env.AGENT_OFFICE_PASSWORD)
17
17
  .option("--opencode-url <url>", "URL of the OpenCode server (default: http://127.0.0.1:4096)", process.env.OPENCODE_URL ?? "http://127.0.0.1:4096")
18
+ .option("--pi-vendor <vendor>", "PI coding vendor (e.g., xai, openai, anthropic). Required when using PI coding server.", process.env.PI_VENDOR)
19
+ .option("--pi-model <model>", "PI coding model name (e.g., grok-code-fast-1). Required when using PI coding server.", process.env.PI_MODEL)
20
+ .option("--pi-api-key <key>", "PI coding API key. Required when using PI coding server.", process.env.PI_API_KEY)
18
21
  .action(async (options) => {
19
22
  const { serve } = await import("./commands/serve.js");
20
23
  await serve(options);
21
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
+ });
22
113
  program
23
114
  .command("manage")
24
115
  .description("[HUMAN ONLY] Launch the interactive TUI to manage sessions")
@@ -136,9 +227,9 @@ cronCmd
136
227
  await listCrons(token);
137
228
  });
138
229
  cronCmd
139
- .command("create")
230
+ .command("request")
140
231
  .argument("<token>", "Agent token in the format <agent_code>@<server-url>")
141
- .description("Create a new cron job")
232
+ .description("Request a new cron job (requires human approval)")
142
233
  .requiredOption("--name <name>", "Cron job name")
143
234
  .requiredOption("--schedule <schedule>", "Cron expression (e.g., '0 9 * * *' for daily at 9am)")
144
235
  .requiredOption("--message <message>", "Action to perform when job fires")
@@ -154,8 +245,16 @@ Warning:
154
245
  The safest option is always single quotes: --message 'your message here'
155
246
  (single quotes prevent all shell interpretation)`)
156
247
  .action(async (token, options) => {
157
- const { createCron } = await import("./commands/worker.js");
158
- await createCron(token, options);
248
+ const { requestCron } = await import("./commands/worker.js");
249
+ await requestCron(token, options);
250
+ });
251
+ cronCmd
252
+ .command("requests")
253
+ .argument("<token>", "Agent token in the format <agent_code>@<server-url>")
254
+ .description("List your cron job requests and their status")
255
+ .action(async (token) => {
256
+ const { listCronRequests } = await import("./commands/worker.js");
257
+ await listCronRequests(token);
159
258
  });
160
259
  cronCmd
161
260
  .command("delete")