agent-office 0.0.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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +41 -0
  5. package/dist/commands/manage.d.ts +5 -0
  6. package/dist/commands/manage.js +20 -0
  7. package/dist/commands/serve.d.ts +9 -0
  8. package/dist/commands/serve.js +54 -0
  9. package/dist/commands/worker.d.ts +1 -0
  10. package/dist/commands/worker.js +50 -0
  11. package/dist/db/index.d.ts +10 -0
  12. package/dist/db/index.js +9 -0
  13. package/dist/db/migrate.d.ts +2 -0
  14. package/dist/db/migrate.js +45 -0
  15. package/dist/lib/opencode.d.ts +7 -0
  16. package/dist/lib/opencode.js +4 -0
  17. package/dist/manage/app.d.ts +6 -0
  18. package/dist/manage/app.js +102 -0
  19. package/dist/manage/components/AgentCode.d.ts +8 -0
  20. package/dist/manage/components/AgentCode.js +73 -0
  21. package/dist/manage/components/CreateSession.d.ts +7 -0
  22. package/dist/manage/components/CreateSession.js +37 -0
  23. package/dist/manage/components/DeleteSession.d.ts +7 -0
  24. package/dist/manage/components/DeleteSession.js +55 -0
  25. package/dist/manage/components/InjectText.d.ts +8 -0
  26. package/dist/manage/components/InjectText.js +51 -0
  27. package/dist/manage/components/SessionList.d.ts +8 -0
  28. package/dist/manage/components/SessionList.js +52 -0
  29. package/dist/manage/components/TailMessages.d.ts +8 -0
  30. package/dist/manage/components/TailMessages.js +77 -0
  31. package/dist/manage/hooks/useApi.d.ts +33 -0
  32. package/dist/manage/hooks/useApi.js +82 -0
  33. package/dist/server/index.d.ts +3 -0
  34. package/dist/server/index.js +22 -0
  35. package/dist/server/routes.d.ts +5 -0
  36. package/dist/server/routes.js +228 -0
  37. package/package.json +50 -0
@@ -0,0 +1,228 @@
1
+ import { Router } from "express";
2
+ export function createRouter(sql, opencode) {
3
+ const router = Router();
4
+ // GET /health
5
+ router.get("/health", (_req, res) => {
6
+ res.json({ ok: true });
7
+ });
8
+ // GET /sessions
9
+ router.get("/sessions", async (_req, res) => {
10
+ try {
11
+ const rows = await sql `
12
+ SELECT id, name, session_id, agent_code, created_at
13
+ FROM sessions
14
+ ORDER BY created_at DESC
15
+ `;
16
+ res.json(rows);
17
+ }
18
+ catch (err) {
19
+ console.error("GET /sessions error:", err);
20
+ res.status(500).json({ error: "Internal server error" });
21
+ }
22
+ });
23
+ // POST /sessions { name }
24
+ router.post("/sessions", async (req, res) => {
25
+ const { name } = req.body;
26
+ if (!name || typeof name !== "string" || !name.trim()) {
27
+ res.status(400).json({ error: "name is required" });
28
+ return;
29
+ }
30
+ const trimmedName = name.trim();
31
+ const existing = await sql `
32
+ SELECT id FROM sessions WHERE name = ${trimmedName}
33
+ `;
34
+ if (existing.length > 0) {
35
+ res.status(409).json({ error: `Session name "${trimmedName}" already exists` });
36
+ return;
37
+ }
38
+ // Create the OpenCode session
39
+ let opencodeSessionId;
40
+ try {
41
+ const session = await opencode.session.create();
42
+ opencodeSessionId = session.id;
43
+ }
44
+ catch (err) {
45
+ console.error("OpenCode session.create error:", err);
46
+ res.status(502).json({ error: "Failed to create OpenCode session", detail: String(err) });
47
+ return;
48
+ }
49
+ // Persist — agent_code auto-generated by Postgres gen_random_uuid()
50
+ try {
51
+ const [row] = await sql `
52
+ INSERT INTO sessions (name, session_id)
53
+ VALUES (${trimmedName}, ${opencodeSessionId})
54
+ RETURNING id, name, session_id, agent_code, created_at
55
+ `;
56
+ res.status(201).json(row);
57
+ }
58
+ catch (err) {
59
+ console.error("DB insert error:", err);
60
+ try {
61
+ await opencode.session.delete(opencodeSessionId);
62
+ }
63
+ catch { /* best-effort */ }
64
+ res.status(500).json({ error: "Internal server error" });
65
+ }
66
+ });
67
+ // POST /sessions/:name/regenerate-code
68
+ router.post("/sessions/:name/regenerate-code", async (req, res) => {
69
+ const { name } = req.params;
70
+ const rows = await sql `
71
+ SELECT id FROM sessions WHERE name = ${name}
72
+ `;
73
+ if (rows.length === 0) {
74
+ res.status(404).json({ error: `Session "${name}" not found` });
75
+ return;
76
+ }
77
+ try {
78
+ const [updated] = await sql `
79
+ UPDATE sessions
80
+ SET agent_code = gen_random_uuid()
81
+ WHERE name = ${name}
82
+ RETURNING id, name, session_id, agent_code, created_at
83
+ `;
84
+ res.json(updated);
85
+ }
86
+ catch (err) {
87
+ console.error("regenerate-code error:", err);
88
+ res.status(500).json({ error: "Internal server error" });
89
+ }
90
+ });
91
+ // GET /sessions/:name/messages?limit=N
92
+ router.get("/sessions/:name/messages", async (req, res) => {
93
+ const { name } = req.params;
94
+ const limit = Math.min(parseInt(req.query.limit ?? "20", 10), 100);
95
+ const rows = await sql `
96
+ SELECT id, name, session_id, agent_code, created_at FROM sessions WHERE name = ${name}
97
+ `;
98
+ if (rows.length === 0) {
99
+ res.status(404).json({ error: `Session "${name}" not found` });
100
+ return;
101
+ }
102
+ const row = rows[0];
103
+ try {
104
+ const messages = await opencode.session.messages(row.session_id);
105
+ const result = messages
106
+ .slice(-limit)
107
+ .map((m) => ({
108
+ role: m.info.role,
109
+ parts: m.parts
110
+ .filter((p) => p.type === "text")
111
+ .map((p) => ({ type: "text", text: p.text })),
112
+ }))
113
+ .filter((m) => m.parts.length > 0);
114
+ res.json(result);
115
+ }
116
+ catch (err) {
117
+ console.error("OpenCode session.messages error:", err);
118
+ res.status(502).json({ error: "Failed to fetch messages from OpenCode", detail: String(err) });
119
+ }
120
+ });
121
+ // POST /sessions/:name/inject { text, modelID?, providerID? }
122
+ router.post("/sessions/:name/inject", async (req, res) => {
123
+ const { name } = req.params;
124
+ const { text, modelID, providerID } = req.body;
125
+ if (!text || typeof text !== "string" || !text.trim()) {
126
+ res.status(400).json({ error: "text is required" });
127
+ return;
128
+ }
129
+ const rows = await sql `
130
+ SELECT id, name, session_id, agent_code, created_at FROM sessions WHERE name = ${name}
131
+ `;
132
+ if (rows.length === 0) {
133
+ res.status(404).json({ error: `Session "${name}" not found` });
134
+ return;
135
+ }
136
+ const row = rows[0];
137
+ let resolvedModelID = modelID;
138
+ let resolvedProviderID = providerID;
139
+ if (!resolvedModelID || !resolvedProviderID) {
140
+ try {
141
+ const providers = await opencode.app.providers();
142
+ const defaultEntry = Object.entries(providers.default)[0];
143
+ if (!defaultEntry) {
144
+ res.status(502).json({ error: "No default model configured in OpenCode" });
145
+ return;
146
+ }
147
+ resolvedModelID = resolvedModelID ?? defaultEntry[0];
148
+ resolvedProviderID = resolvedProviderID ?? defaultEntry[1];
149
+ }
150
+ catch (err) {
151
+ console.error("OpenCode app.providers error:", err);
152
+ res.status(502).json({ error: "Failed to fetch providers from OpenCode", detail: String(err) });
153
+ return;
154
+ }
155
+ }
156
+ try {
157
+ const response = await opencode.session.chat(row.session_id, {
158
+ modelID: resolvedModelID,
159
+ providerID: resolvedProviderID,
160
+ parts: [{ type: "text", text: text.trim() }],
161
+ });
162
+ res.json({ ok: true, messageID: response.id });
163
+ }
164
+ catch (err) {
165
+ console.error("OpenCode session.chat error:", err);
166
+ res.status(502).json({ error: "Failed to inject message into OpenCode session", detail: String(err) });
167
+ }
168
+ });
169
+ // DELETE /sessions/:name
170
+ router.delete("/sessions/:name", async (req, res) => {
171
+ const { name } = req.params;
172
+ const rows = await sql `
173
+ SELECT id, name, session_id, agent_code, created_at FROM sessions WHERE name = ${name}
174
+ `;
175
+ if (rows.length === 0) {
176
+ res.status(404).json({ error: `Session "${name}" not found` });
177
+ return;
178
+ }
179
+ const row = rows[0];
180
+ try {
181
+ await opencode.session.delete(row.session_id);
182
+ }
183
+ catch (err) {
184
+ console.error("OpenCode session.delete error:", err);
185
+ res.status(502).json({ error: "Failed to delete OpenCode session", detail: String(err) });
186
+ return;
187
+ }
188
+ try {
189
+ await sql `DELETE FROM sessions WHERE id = ${row.id}`;
190
+ res.json({ deleted: true, name: row.name, session_id: row.session_id });
191
+ }
192
+ catch (err) {
193
+ console.error("DB delete error:", err);
194
+ res.status(500).json({ error: "Internal server error" });
195
+ }
196
+ });
197
+ return router;
198
+ }
199
+ // Separate unauthenticated router for worker endpoints
200
+ export function createWorkerRouter(sql) {
201
+ const router = Router();
202
+ // GET /worker/clock-in — no Bearer auth, validated by agent_code
203
+ // Usage: agent-office worker clock-in <agent_code>@<url>
204
+ router.get("/worker/clock-in", async (req, res) => {
205
+ const { code } = req.query;
206
+ if (!code || typeof code !== "string") {
207
+ res.status(400).json({ error: "code query parameter is required" });
208
+ return;
209
+ }
210
+ const rows = await sql `
211
+ SELECT id, name, session_id, agent_code, created_at
212
+ FROM sessions
213
+ WHERE agent_code = ${code}
214
+ `;
215
+ if (rows.length === 0) {
216
+ res.status(401).json({ error: "Invalid agent code" });
217
+ return;
218
+ }
219
+ const session = rows[0];
220
+ res.json({
221
+ ok: true,
222
+ name: session.name,
223
+ session_id: session.session_id,
224
+ message: `Welcome to the agent office, your name is ${session.name}. Your OpenCode session ID is ${session.session_id}. You are now clocked in and ready to work.`,
225
+ });
226
+ });
227
+ return router;
228
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "agent-office",
3
+ "version": "0.0.0",
4
+ "description": "Manage OpenCode sessions with named aliases",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Richard Anaya",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/innercontext/agent-office"
11
+ },
12
+ "homepage": "https://github.com/innercontext/agent-office#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/innercontext/agent-office/issues"
15
+ },
16
+ "bin": {
17
+ "agent-office": "./dist/cli.js"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "dev:serve": "tsx src/cli.ts serve",
24
+ "dev:manage": "tsx src/cli.ts manage",
25
+ "dev:worker": "tsx src/cli.ts worker",
26
+ "build": "tsc",
27
+ "clean": "rm -rf dist",
28
+ "prepublishOnly": "npm run clean && npm run build"
29
+ },
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "dependencies": {
34
+ "@inkjs/ui": "^2.0.0",
35
+ "@opencode-ai/sdk": "^0.1.0-alpha.21",
36
+ "commander": "^12.0.0",
37
+ "dotenv": "^16.0.0",
38
+ "express": "^4.18.0",
39
+ "ink": "^5.0.0",
40
+ "postgres": "^3.4.0",
41
+ "react": "^18.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/express": "^4.17.0",
45
+ "@types/node": "^20.0.0",
46
+ "@types/react": "^18.0.0",
47
+ "tsx": "^4.0.0",
48
+ "typescript": "^5.0.0"
49
+ }
50
+ }