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.
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +41 -0
- package/dist/commands/manage.d.ts +5 -0
- package/dist/commands/manage.js +20 -0
- package/dist/commands/serve.d.ts +9 -0
- package/dist/commands/serve.js +54 -0
- package/dist/commands/worker.d.ts +1 -0
- package/dist/commands/worker.js +50 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.js +9 -0
- package/dist/db/migrate.d.ts +2 -0
- package/dist/db/migrate.js +45 -0
- package/dist/lib/opencode.d.ts +7 -0
- package/dist/lib/opencode.js +4 -0
- package/dist/manage/app.d.ts +6 -0
- package/dist/manage/app.js +102 -0
- package/dist/manage/components/AgentCode.d.ts +8 -0
- package/dist/manage/components/AgentCode.js +73 -0
- package/dist/manage/components/CreateSession.d.ts +7 -0
- package/dist/manage/components/CreateSession.js +37 -0
- package/dist/manage/components/DeleteSession.d.ts +7 -0
- package/dist/manage/components/DeleteSession.js +55 -0
- package/dist/manage/components/InjectText.d.ts +8 -0
- package/dist/manage/components/InjectText.js +51 -0
- package/dist/manage/components/SessionList.d.ts +8 -0
- package/dist/manage/components/SessionList.js +52 -0
- package/dist/manage/components/TailMessages.d.ts +8 -0
- package/dist/manage/components/TailMessages.js +77 -0
- package/dist/manage/hooks/useApi.d.ts +33 -0
- package/dist/manage/hooks/useApi.js +82 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +22 -0
- package/dist/server/routes.d.ts +5 -0
- package/dist/server/routes.js +228 -0
- 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
|
+
}
|