agent-office 0.0.2 → 0.0.4
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/dist/cli.js +64 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +18 -1
- package/dist/commands/worker.d.ts +5 -0
- package/dist/commands/worker.js +67 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/migrate.js +7 -0
- package/dist/manage/app.js +1 -1
- package/dist/manage/components/SessionList.js +353 -7
- package/dist/manage/components/SessionSidebar.js +7 -1
- package/dist/manage/components/TailMessages.js +54 -5
- package/dist/manage/hooks/useApi.d.ts +35 -2
- package/dist/manage/hooks/useApi.js +28 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +3 -3
- package/dist/server/memory.d.ts +64 -0
- package/dist/server/memory.js +214 -0
- package/dist/server/routes.d.ts +3 -2
- package/dist/server/routes.js +479 -14
- package/package.json +4 -1
package/dist/server/routes.js
CHANGED
|
@@ -4,13 +4,130 @@ const MAIL_INJECTION_BLURB = [
|
|
|
4
4
|
``,
|
|
5
5
|
`---`,
|
|
6
6
|
`You have a new message. Please review the injected message above and respond accordingly.`,
|
|
7
|
-
`
|
|
8
|
-
`
|
|
9
|
-
`
|
|
10
|
-
|
|
11
|
-
`
|
|
7
|
+
`IMPORTANT: When reading or responding, note that dollar signs ($) and other special`,
|
|
8
|
+
`characters may be interpreted as markdown. The sender may have included them but they`,
|
|
9
|
+
`could appear differently in your session view. Interpret context accordingly.`,
|
|
10
|
+
``,
|
|
11
|
+
`When responding to the sender:`,
|
|
12
|
+
`- Use the \`agent-office worker send-message\` tool so they can see your reply`,
|
|
13
|
+
`- Avoid excessive length - keep responses concise`,
|
|
14
|
+
`- Use markdown front-matter with a "choices" array if offering options`,
|
|
15
|
+
``,
|
|
16
|
+
`Tip: For currency or prices, use code blocks. Example: put numbers in single or`,
|
|
17
|
+
`double quotes to preserve formatting characters like dollar signs.`,
|
|
12
18
|
].join("\n");
|
|
13
|
-
|
|
19
|
+
function generateWelcomeMessage(name, mode, status, humanName, humanDescription, token) {
|
|
20
|
+
return [
|
|
21
|
+
`╔══════════════════════════════════════════════════════╗`,
|
|
22
|
+
`║ WELCOME TO THE AGENT OFFICE ║`,
|
|
23
|
+
`╚══════════════════════════════════════════════════════╝`,
|
|
24
|
+
``,
|
|
25
|
+
`You are now clocked in.`,
|
|
26
|
+
` Name: ${name}`,
|
|
27
|
+
...(mode ? [` Mode: ${mode}`] : []),
|
|
28
|
+
...(status ? [` Status: ${status}`] : []),
|
|
29
|
+
` Human manager: ${humanName} — the human who created your`,
|
|
30
|
+
` session, assigns your work, and is your`,
|
|
31
|
+
` primary point of contact for questions,`,
|
|
32
|
+
` updates, and decisions.`,
|
|
33
|
+
...(humanDescription ? [
|
|
34
|
+
` "${humanDescription}"`,
|
|
35
|
+
] : []),
|
|
36
|
+
``,
|
|
37
|
+
`The agent-office CLI is your PRIMARY means of communicating`,
|
|
38
|
+
`with your human manager (${humanName}) and your coworkers.`,
|
|
39
|
+
`Use it to send and receive messages, and to discover who`,
|
|
40
|
+
`else is working.`,
|
|
41
|
+
``,
|
|
42
|
+
`════════════════════════════════════════════════════════`,
|
|
43
|
+
` AVAILABLE COMMANDS`,
|
|
44
|
+
`════════════════════════════════════════════════════════`,
|
|
45
|
+
``,
|
|
46
|
+
` List your coworkers`,
|
|
47
|
+
` agent-office worker list-coworkers \\`,
|
|
48
|
+
` ${token}`,
|
|
49
|
+
``,
|
|
50
|
+
` Set your public status (visible to coworkers and manager)`,
|
|
51
|
+
` agent-office worker set-status \\`,
|
|
52
|
+
` --status "your status here" \\`,
|
|
53
|
+
` ${token}`,
|
|
54
|
+
``,
|
|
55
|
+
` Clear your public status`,
|
|
56
|
+
` agent-office worker set-status \\`,
|
|
57
|
+
` --clear \\`,
|
|
58
|
+
` ${token}`,
|
|
59
|
+
``,
|
|
60
|
+
` Send a message to your manager or a coworker`,
|
|
61
|
+
` agent-office worker send-message \\`,
|
|
62
|
+
` --name <recipient-name> \\`,
|
|
63
|
+
` --body "Your message here" \\`,
|
|
64
|
+
` ${token}`,
|
|
65
|
+
``,
|
|
66
|
+
` Send a message to multiple recipients at once`,
|
|
67
|
+
` agent-office worker send-message \\`,
|
|
68
|
+
` --name alice --name bob \\`,
|
|
69
|
+
` --body "Your message here" \\`,
|
|
70
|
+
` ${token}`,
|
|
71
|
+
``,
|
|
72
|
+
` Manage scheduled tasks (optional)`,
|
|
73
|
+
` agent-office worker cron \\`,
|
|
74
|
+
` ${token}`,
|
|
75
|
+
``,
|
|
76
|
+
` Store a memory (persistent across sessions)`,
|
|
77
|
+
` agent-office worker memory add \\`,
|
|
78
|
+
` --content "your memory here" \\`,
|
|
79
|
+
` ${token}`,
|
|
80
|
+
``,
|
|
81
|
+
` Search your memories`,
|
|
82
|
+
` agent-office worker memory search \\`,
|
|
83
|
+
` --query "your search" \\`,
|
|
84
|
+
` ${token}`,
|
|
85
|
+
``,
|
|
86
|
+
` List all your memories`,
|
|
87
|
+
` agent-office worker memory list \\`,
|
|
88
|
+
` ${token}`,
|
|
89
|
+
``,
|
|
90
|
+
` Forget a memory`,
|
|
91
|
+
` agent-office worker memory forget \\`,
|
|
92
|
+
` ${token} <memory-id>`,
|
|
93
|
+
``,
|
|
94
|
+
`════════════════════════════════════════════════════════`,
|
|
95
|
+
` ⚠ IMPORTANT: YOUR SESSIONS ARE PRIVATE`,
|
|
96
|
+
`════════════════════════════════════════════════════════`,
|
|
97
|
+
``,
|
|
98
|
+
` Nobody — not ${humanName}, not your coworkers — can see`,
|
|
99
|
+
` anything you think, reason, or write inside this session.`,
|
|
100
|
+
` Your work is completely private until you explicitly send`,
|
|
101
|
+
` a message using the send-message command.`,
|
|
102
|
+
``,
|
|
103
|
+
` This means:`,
|
|
104
|
+
` • You must send a message to report progress or ask`,
|
|
105
|
+
` for help. Silence looks like inaction.`,
|
|
106
|
+
` • Finishing a task without messaging ${humanName}`,
|
|
107
|
+
` means they will never know it is done.`,
|
|
108
|
+
` • If you are blocked or uncertain, send a message —`,
|
|
109
|
+
` nobody will know otherwise.`,
|
|
110
|
+
``,
|
|
111
|
+
`════════════════════════════════════════════════════════`,
|
|
112
|
+
` TIPS`,
|
|
113
|
+
`════════════════════════════════════════════════════════`,
|
|
114
|
+
``,
|
|
115
|
+
` - Run list-coworkers to discover who is available and`,
|
|
116
|
+
` what their names are before sending messages.`,
|
|
117
|
+
` - Messages you send are delivered directly into the`,
|
|
118
|
+
` recipient's active session — they will see them`,
|
|
119
|
+
` immediately.`,
|
|
120
|
+
` - Your human manager is ${humanName}. They can send you`,
|
|
121
|
+
` messages at any time and those will appear here in`,
|
|
122
|
+
` your session just like this one. You can reach them`,
|
|
123
|
+
` by sending a message to --name ${humanName}.`,
|
|
124
|
+
` - Optional: Set up recurring scheduled tasks with cron`,
|
|
125
|
+
` jobs. Run 'agent-office worker cron list ${token}' to`,
|
|
126
|
+
` get started.`,
|
|
127
|
+
``,
|
|
128
|
+
].join("\n");
|
|
129
|
+
}
|
|
130
|
+
export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager) {
|
|
14
131
|
const router = Router();
|
|
15
132
|
router.get("/health", (_req, res) => {
|
|
16
133
|
res.json({ ok: true });
|
|
@@ -36,7 +153,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler) {
|
|
|
36
153
|
router.get("/sessions", async (_req, res) => {
|
|
37
154
|
try {
|
|
38
155
|
const rows = await sql `
|
|
39
|
-
SELECT id, name, session_id, agent_code, mode, created_at
|
|
156
|
+
SELECT id, name, session_id, agent_code, mode, status, created_at
|
|
40
157
|
FROM sessions
|
|
41
158
|
ORDER BY created_at DESC
|
|
42
159
|
`;
|
|
@@ -95,12 +212,11 @@ export function createRouter(sql, opencode, serverUrl, scheduler) {
|
|
|
95
212
|
const defaultEntry = Object.entries(providers.default)[0];
|
|
96
213
|
if (defaultEntry) {
|
|
97
214
|
const clockInToken = `${row.agent_code}@${serverUrl}`;
|
|
98
|
-
const
|
|
99
|
-
const firstMessage = `You have been enrolled in the agent office.${modeNote}\n\nTo clock in and receive your full briefing, run:\n\n agent-office worker clock-in ${clockInToken}`;
|
|
215
|
+
const enrollmentMessage = `You have been enrolled in the agent office.\n\nTo clock in and receive your full briefing, run:\n\n agent-office worker clock-in ${clockInToken}`;
|
|
100
216
|
await opencode.session.chat(opencodeSessionId, {
|
|
101
217
|
modelID: defaultEntry[0],
|
|
102
218
|
providerID: defaultEntry[1],
|
|
103
|
-
parts: [{ type: "text", text:
|
|
219
|
+
parts: [{ type: "text", text: enrollmentMessage }],
|
|
104
220
|
...(trimmedMode ? { mode: trimmedMode } : {}),
|
|
105
221
|
});
|
|
106
222
|
}
|
|
@@ -150,9 +266,26 @@ export function createRouter(sql, opencode, serverUrl, scheduler) {
|
|
|
150
266
|
.slice(-limit)
|
|
151
267
|
.map((m) => ({
|
|
152
268
|
role: m.info.role,
|
|
153
|
-
parts: m.parts
|
|
154
|
-
|
|
155
|
-
|
|
269
|
+
parts: m.parts.map((p) => {
|
|
270
|
+
const part = p;
|
|
271
|
+
if (part.type === "text") {
|
|
272
|
+
return { type: "text", text: part.text ?? "" };
|
|
273
|
+
}
|
|
274
|
+
else if (part.type === "tool") {
|
|
275
|
+
// Extract tool name, input, and output from the tool state
|
|
276
|
+
const state = part.state;
|
|
277
|
+
return {
|
|
278
|
+
type: "tool",
|
|
279
|
+
tool: part.tool,
|
|
280
|
+
input: state?.input,
|
|
281
|
+
output: state?.output,
|
|
282
|
+
data: part,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
return { type: part.type, data: part };
|
|
287
|
+
}
|
|
288
|
+
}),
|
|
156
289
|
}))
|
|
157
290
|
.filter((m) => m.parts.length > 0);
|
|
158
291
|
res.json(result);
|
|
@@ -209,6 +342,48 @@ export function createRouter(sql, opencode, serverUrl, scheduler) {
|
|
|
209
342
|
res.status(502).json({ error: "Failed to inject message into OpenCode session", detail: String(err) });
|
|
210
343
|
}
|
|
211
344
|
});
|
|
345
|
+
router.post("/sessions/:name/revert-to-start", async (req, res) => {
|
|
346
|
+
const { name } = req.params;
|
|
347
|
+
const rows = await sql `
|
|
348
|
+
SELECT id, name, session_id, agent_code, mode, status, created_at FROM sessions WHERE name = ${name}
|
|
349
|
+
`;
|
|
350
|
+
if (rows.length === 0) {
|
|
351
|
+
res.status(404).json({ error: `Session "${name}" not found` });
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const session = rows[0];
|
|
355
|
+
try {
|
|
356
|
+
const messages = await opencode.session.messages(session.session_id);
|
|
357
|
+
if (messages.length === 0) {
|
|
358
|
+
res.status(400).json({ error: "Session has no messages to revert to" });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const firstMessage = messages[0];
|
|
362
|
+
if (!firstMessage || !firstMessage.info || !firstMessage.info.id) {
|
|
363
|
+
res.status(500).json({ error: "Failed to get first message ID" });
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
await opencode.session.revert(session.session_id, { messageID: firstMessage.info.id });
|
|
367
|
+
const providers = await opencode.app.providers();
|
|
368
|
+
const defaultEntry = Object.entries(providers.default)[0];
|
|
369
|
+
if (!defaultEntry) {
|
|
370
|
+
res.status(502).json({ error: "No default model configured in OpenCode" });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const clockInToken = `${session.agent_code}@${serverUrl}`;
|
|
374
|
+
const enrollmentMessage = `You have been enrolled in the agent office.\n\nTo clock in and receive your full briefing, run:\n\n agent-office worker clock-in ${clockInToken}`;
|
|
375
|
+
await opencode.session.chat(session.session_id, {
|
|
376
|
+
modelID: defaultEntry[0],
|
|
377
|
+
providerID: defaultEntry[1],
|
|
378
|
+
parts: [{ type: "text", text: enrollmentMessage }],
|
|
379
|
+
});
|
|
380
|
+
res.json({ ok: true, messageID: firstMessage.info.id });
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
console.error("POST /sessions/:name/revert-to-start error:", err);
|
|
384
|
+
res.status(502).json({ error: "Failed to revert session", detail: String(err) });
|
|
385
|
+
}
|
|
386
|
+
});
|
|
212
387
|
router.delete("/sessions/:name", async (req, res) => {
|
|
213
388
|
const { name } = req.params;
|
|
214
389
|
const rows = await sql `
|
|
@@ -642,9 +817,135 @@ export function createRouter(sql, opencode, serverUrl, scheduler) {
|
|
|
642
817
|
res.status(500).json({ error: "Internal server error" });
|
|
643
818
|
}
|
|
644
819
|
});
|
|
820
|
+
// ── Memory Endpoints (authenticated, for manage TUI) ───────────────────────
|
|
821
|
+
router.get("/sessions/:name/memories", async (req, res) => {
|
|
822
|
+
const { name } = req.params;
|
|
823
|
+
const limit = Math.min(parseInt(req.query.limit ?? "50", 10), 200);
|
|
824
|
+
const rows = await sql `SELECT id FROM sessions WHERE name = ${name}`;
|
|
825
|
+
if (rows.length === 0) {
|
|
826
|
+
res.status(404).json({ error: `Session "${name}" not found` });
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
const memories = memoryManager.listMemories(name, limit);
|
|
831
|
+
const stats = memoryManager.getStats(name);
|
|
832
|
+
res.json({ memories, total: stats.total });
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
console.error("GET /sessions/:name/memories error:", err);
|
|
836
|
+
res.status(500).json({ error: "Internal server error" });
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
router.post("/sessions/:name/memories", async (req, res) => {
|
|
840
|
+
const { name } = req.params;
|
|
841
|
+
const { content, metadata } = req.body;
|
|
842
|
+
if (!content || typeof content !== "string" || !content.trim()) {
|
|
843
|
+
res.status(400).json({ error: "content is required" });
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const rows = await sql `SELECT id FROM sessions WHERE name = ${name}`;
|
|
847
|
+
if (rows.length === 0) {
|
|
848
|
+
res.status(404).json({ error: `Session "${name}" not found` });
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
const id = await memoryManager.addMemory(name, content.trim(), metadata ?? {});
|
|
853
|
+
res.status(201).json({ ok: true, id });
|
|
854
|
+
}
|
|
855
|
+
catch (err) {
|
|
856
|
+
console.error("POST /sessions/:name/memories error:", err);
|
|
857
|
+
res.status(500).json({ error: "Internal server error" });
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
router.get("/sessions/:name/memories/:memoryId", async (req, res) => {
|
|
861
|
+
const { name, memoryId } = req.params;
|
|
862
|
+
const rows = await sql `SELECT id FROM sessions WHERE name = ${name}`;
|
|
863
|
+
if (rows.length === 0) {
|
|
864
|
+
res.status(404).json({ error: `Session "${name}" not found` });
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
try {
|
|
868
|
+
const memory = memoryManager.getMemory(name, memoryId);
|
|
869
|
+
if (!memory) {
|
|
870
|
+
res.status(404).json({ error: "Memory not found" });
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
res.json(memory);
|
|
874
|
+
}
|
|
875
|
+
catch (err) {
|
|
876
|
+
console.error("GET /sessions/:name/memories/:memoryId error:", err);
|
|
877
|
+
res.status(500).json({ error: "Internal server error" });
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
router.put("/sessions/:name/memories/:memoryId", async (req, res) => {
|
|
881
|
+
const { name, memoryId } = req.params;
|
|
882
|
+
const { content, metadata } = req.body;
|
|
883
|
+
if (!content || typeof content !== "string" || !content.trim()) {
|
|
884
|
+
res.status(400).json({ error: "content is required" });
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const rows = await sql `SELECT id FROM sessions WHERE name = ${name}`;
|
|
888
|
+
if (rows.length === 0) {
|
|
889
|
+
res.status(404).json({ error: `Session "${name}" not found` });
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
try {
|
|
893
|
+
const updated = await memoryManager.updateMemory(name, memoryId, content.trim(), metadata);
|
|
894
|
+
if (!updated) {
|
|
895
|
+
res.status(404).json({ error: "Memory not found" });
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
res.json({ ok: true });
|
|
899
|
+
}
|
|
900
|
+
catch (err) {
|
|
901
|
+
console.error("PUT /sessions/:name/memories/:memoryId error:", err);
|
|
902
|
+
res.status(500).json({ error: "Internal server error" });
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
router.delete("/sessions/:name/memories/:memoryId", async (req, res) => {
|
|
906
|
+
const { name, memoryId } = req.params;
|
|
907
|
+
const rows = await sql `SELECT id FROM sessions WHERE name = ${name}`;
|
|
908
|
+
if (rows.length === 0) {
|
|
909
|
+
res.status(404).json({ error: `Session "${name}" not found` });
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const deleted = memoryManager.deleteMemory(name, memoryId);
|
|
914
|
+
if (!deleted) {
|
|
915
|
+
res.status(404).json({ error: "Memory not found" });
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
res.json({ deleted: true, id: memoryId });
|
|
919
|
+
}
|
|
920
|
+
catch (err) {
|
|
921
|
+
console.error("DELETE /sessions/:name/memories/:memoryId error:", err);
|
|
922
|
+
res.status(500).json({ error: "Internal server error" });
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
router.post("/sessions/:name/memories/search", async (req, res) => {
|
|
926
|
+
const { name } = req.params;
|
|
927
|
+
const { query, limit } = req.body;
|
|
928
|
+
if (!query || typeof query !== "string" || !query.trim()) {
|
|
929
|
+
res.status(400).json({ error: "query is required" });
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const rows = await sql `SELECT id FROM sessions WHERE name = ${name}`;
|
|
933
|
+
if (rows.length === 0) {
|
|
934
|
+
res.status(404).json({ error: `Session "${name}" not found` });
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
try {
|
|
938
|
+
const results = await memoryManager.searchMemories(name, query.trim(), Math.min(limit ?? 10, 50));
|
|
939
|
+
res.json(results);
|
|
940
|
+
}
|
|
941
|
+
catch (err) {
|
|
942
|
+
console.error("POST /sessions/:name/memories/search error:", err);
|
|
943
|
+
res.status(500).json({ error: "Internal server error" });
|
|
944
|
+
}
|
|
945
|
+
});
|
|
645
946
|
return router;
|
|
646
947
|
}
|
|
647
|
-
export function createWorkerRouter(sql, opencode, serverUrl) {
|
|
948
|
+
export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
|
|
648
949
|
const router = Router();
|
|
649
950
|
router.get("/worker/clock-in", async (req, res) => {
|
|
650
951
|
const { code } = req.query;
|
|
@@ -713,6 +1014,24 @@ export function createWorkerRouter(sql, opencode, serverUrl) {
|
|
|
713
1014
|
` agent-office worker cron \\`,
|
|
714
1015
|
` ${token}`,
|
|
715
1016
|
``,
|
|
1017
|
+
` Store a memory (persistent across sessions)`,
|
|
1018
|
+
` agent-office worker memory add \\`,
|
|
1019
|
+
` --content "your memory here" \\`,
|
|
1020
|
+
` ${token}`,
|
|
1021
|
+
``,
|
|
1022
|
+
` Search your memories`,
|
|
1023
|
+
` agent-office worker memory search \\`,
|
|
1024
|
+
` --query "your search" \\`,
|
|
1025
|
+
` ${token}`,
|
|
1026
|
+
``,
|
|
1027
|
+
` List all your memories`,
|
|
1028
|
+
` agent-office worker memory list \\`,
|
|
1029
|
+
` ${token}`,
|
|
1030
|
+
``,
|
|
1031
|
+
` Forget a memory`,
|
|
1032
|
+
` agent-office worker memory forget \\`,
|
|
1033
|
+
` ${token} <memory-id>`,
|
|
1034
|
+
``,
|
|
716
1035
|
`════════════════════════════════════════════════════════`,
|
|
717
1036
|
` ⚠ IMPORTANT: YOUR SESSIONS ARE PRIVATE`,
|
|
718
1037
|
`════════════════════════════════════════════════════════`,
|
|
@@ -784,6 +1103,42 @@ export function createWorkerRouter(sql, opencode, serverUrl) {
|
|
|
784
1103
|
res.status(500).json({ error: "Internal server error" });
|
|
785
1104
|
}
|
|
786
1105
|
});
|
|
1106
|
+
router.post("/worker/set-status", async (req, res) => {
|
|
1107
|
+
const { code } = req.query;
|
|
1108
|
+
const { status } = req.body;
|
|
1109
|
+
if (!code || typeof code !== "string") {
|
|
1110
|
+
res.status(400).json({ error: "code query parameter is required" });
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
const trimmedStatus = status === null ? null : (status === undefined ? null : status.trim());
|
|
1114
|
+
if (trimmedStatus !== null && trimmedStatus.length > 140) {
|
|
1115
|
+
res.status(400).json({ error: "status must be at most 140 characters" });
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const rows = await sql `
|
|
1119
|
+
SELECT id, name, session_id, agent_code, mode, status, created_at
|
|
1120
|
+
FROM sessions
|
|
1121
|
+
WHERE agent_code = ${code}
|
|
1122
|
+
`;
|
|
1123
|
+
if (rows.length === 0) {
|
|
1124
|
+
res.status(401).json({ error: "Invalid agent code" });
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const session = rows[0];
|
|
1128
|
+
try {
|
|
1129
|
+
await sql `
|
|
1130
|
+
UPDATE sessions
|
|
1131
|
+
SET status = ${trimmedStatus}
|
|
1132
|
+
WHERE agent_code = ${code}
|
|
1133
|
+
`;
|
|
1134
|
+
}
|
|
1135
|
+
catch (err) {
|
|
1136
|
+
console.error("POST /worker/set-status error:", err);
|
|
1137
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
res.json({ ok: true, name: session.name, status: trimmedStatus });
|
|
1141
|
+
});
|
|
787
1142
|
router.post("/worker/send-message", async (req, res) => {
|
|
788
1143
|
const { code } = req.query;
|
|
789
1144
|
const { to, body } = req.body;
|
|
@@ -1177,5 +1532,115 @@ export function createWorkerRouter(sql, opencode, serverUrl) {
|
|
|
1177
1532
|
res.status(500).json({ error: "Internal server error" });
|
|
1178
1533
|
}
|
|
1179
1534
|
});
|
|
1535
|
+
// ── Worker Memory Endpoints (authenticated via agent_code in query) ────────
|
|
1536
|
+
router.post("/worker/memory/add", async (req, res) => {
|
|
1537
|
+
const { code } = req.query;
|
|
1538
|
+
const { content, metadata } = req.body;
|
|
1539
|
+
if (!code || typeof code !== "string") {
|
|
1540
|
+
res.status(400).json({ error: "code query parameter is required" });
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (!content || typeof content !== "string" || !content.trim()) {
|
|
1544
|
+
res.status(400).json({ error: "content is required" });
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const session = await sql `
|
|
1548
|
+
SELECT name FROM sessions WHERE agent_code = ${code}
|
|
1549
|
+
`;
|
|
1550
|
+
if (session.length === 0) {
|
|
1551
|
+
res.status(401).json({ error: "Invalid agent code" });
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
const sessionName = session[0].name;
|
|
1555
|
+
try {
|
|
1556
|
+
const id = await memoryManager.addMemory(sessionName, content.trim(), metadata ?? {});
|
|
1557
|
+
res.status(201).json({ ok: true, id });
|
|
1558
|
+
}
|
|
1559
|
+
catch (err) {
|
|
1560
|
+
console.error("POST /worker/memory/add error:", err);
|
|
1561
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
router.post("/worker/memory/search", async (req, res) => {
|
|
1565
|
+
const { code } = req.query;
|
|
1566
|
+
const { query, limit } = req.body;
|
|
1567
|
+
if (!code || typeof code !== "string") {
|
|
1568
|
+
res.status(400).json({ error: "code query parameter is required" });
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
if (!query || typeof query !== "string" || !query.trim()) {
|
|
1572
|
+
res.status(400).json({ error: "query is required" });
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
const session = await sql `
|
|
1576
|
+
SELECT name FROM sessions WHERE agent_code = ${code}
|
|
1577
|
+
`;
|
|
1578
|
+
if (session.length === 0) {
|
|
1579
|
+
res.status(401).json({ error: "Invalid agent code" });
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
const sessionName = session[0].name;
|
|
1583
|
+
try {
|
|
1584
|
+
const results = await memoryManager.searchMemories(sessionName, query.trim(), Math.min(limit ?? 10, 50));
|
|
1585
|
+
res.json(results);
|
|
1586
|
+
}
|
|
1587
|
+
catch (err) {
|
|
1588
|
+
console.error("POST /worker/memory/search error:", err);
|
|
1589
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
router.get("/worker/memory/list", async (req, res) => {
|
|
1593
|
+
const { code, limit: limitStr } = req.query;
|
|
1594
|
+
if (!code || typeof code !== "string") {
|
|
1595
|
+
res.status(400).json({ error: "code query parameter is required" });
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const session = await sql `
|
|
1599
|
+
SELECT name FROM sessions WHERE agent_code = ${code}
|
|
1600
|
+
`;
|
|
1601
|
+
if (session.length === 0) {
|
|
1602
|
+
res.status(401).json({ error: "Invalid agent code" });
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
const sessionName = session[0].name;
|
|
1606
|
+
const limit = Math.min(parseInt(limitStr ?? "50", 10), 200);
|
|
1607
|
+
try {
|
|
1608
|
+
const memories = memoryManager.listMemories(sessionName, limit);
|
|
1609
|
+
const stats = memoryManager.getStats(sessionName);
|
|
1610
|
+
res.json({ memories, total: stats.total });
|
|
1611
|
+
}
|
|
1612
|
+
catch (err) {
|
|
1613
|
+
console.error("GET /worker/memory/list error:", err);
|
|
1614
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
router.delete("/worker/memory/:memoryId", async (req, res) => {
|
|
1618
|
+
const { code } = req.query;
|
|
1619
|
+
const { memoryId } = req.params;
|
|
1620
|
+
if (!code || typeof code !== "string") {
|
|
1621
|
+
res.status(400).json({ error: "code query parameter is required" });
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const session = await sql `
|
|
1625
|
+
SELECT name FROM sessions WHERE agent_code = ${code}
|
|
1626
|
+
`;
|
|
1627
|
+
if (session.length === 0) {
|
|
1628
|
+
res.status(401).json({ error: "Invalid agent code" });
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
const sessionName = session[0].name;
|
|
1632
|
+
try {
|
|
1633
|
+
const deleted = memoryManager.deleteMemory(sessionName, memoryId);
|
|
1634
|
+
if (!deleted) {
|
|
1635
|
+
res.status(404).json({ error: "Memory not found" });
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
res.json({ deleted: true, id: memoryId });
|
|
1639
|
+
}
|
|
1640
|
+
catch (err) {
|
|
1641
|
+
console.error("DELETE /worker/memory/:memoryId error:", err);
|
|
1642
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1180
1645
|
return router;
|
|
1181
1646
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-office",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Manage OpenCode sessions with named aliases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,15 +33,18 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@inkjs/ui": "^2.0.0",
|
|
35
35
|
"@opencode-ai/sdk": "^0.1.0-alpha.21",
|
|
36
|
+
"agent-office": "^0.0.2",
|
|
36
37
|
"commander": "^12.0.0",
|
|
37
38
|
"croner": "^10.0.1",
|
|
38
39
|
"dotenv": "^16.0.0",
|
|
39
40
|
"express": "^4.18.0",
|
|
41
|
+
"fastmemory": "^0.1.5",
|
|
40
42
|
"ink": "^5.0.0",
|
|
41
43
|
"postgres": "^3.4.0",
|
|
42
44
|
"react": "^18.0.0"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
47
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
45
48
|
"@types/express": "^4.17.0",
|
|
46
49
|
"@types/node": "^20.0.0",
|
|
47
50
|
"@types/react": "^18.0.0",
|