agent-office 0.0.14 → 0.0.16

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 CHANGED
@@ -50,14 +50,6 @@ communicatorCmd
50
50
  const workerCmd = program
51
51
  .command("worker")
52
52
  .description("Worker agent commands");
53
- workerCmd
54
- .command("clock-in")
55
- .description("Clock in as a worker agent")
56
- .argument("<token>", "Agent token in the format <agent_code>@<server-url>")
57
- .action(async (token) => {
58
- const { clockIn } = await import("./commands/worker.js");
59
- await clockIn(token);
60
- });
61
53
  workerCmd
62
54
  .command("list-coworkers")
63
55
  .description("List all other workers (coworkers)")
@@ -57,7 +57,7 @@ export async function serve(options) {
57
57
  // Create Express app
58
58
  const app = createApp(sql, agenticCodingServer, password, serverUrl, cronScheduler, memoryManager);
59
59
  // Start cron scheduler
60
- await cronScheduler.start(sql, agenticCodingServer);
60
+ await cronScheduler.start(sql, agenticCodingServer, serverUrl);
61
61
  // Start server
62
62
  const server = app.listen(port, options.host, () => {
63
63
  console.log(`agent-office server listening on ${serverUrl}`);
@@ -1,4 +1,3 @@
1
- export declare function clockIn(token: string): Promise<void>;
2
1
  export declare function listCoworkers(token: string): Promise<void>;
3
2
  export declare function setStatus(token: string, status: string | null): Promise<void>;
4
3
  export declare function sendMessage(token: string, recipients: string[], body: string): Promise<void>;
@@ -79,35 +79,6 @@ async function postWorker(token, endpoint, payload) {
79
79
  }
80
80
  return body;
81
81
  }
82
- export async function clockIn(token) {
83
- const { agentCode, serverUrl } = parseToken(token);
84
- const parsedUrl = new URL(serverUrl);
85
- const clockInUrl = `${parsedUrl.origin}/worker/clock-in?code=${encodeURIComponent(agentCode)}`;
86
- let res;
87
- try {
88
- res = await fetch(clockInUrl);
89
- }
90
- catch (err) {
91
- console.error(`Error: could not reach ${parsedUrl.origin}`);
92
- console.error(err instanceof Error ? err.message : String(err));
93
- process.exit(1);
94
- }
95
- let body;
96
- try {
97
- body = await res.json();
98
- }
99
- catch {
100
- console.error(`Error: invalid response from server`);
101
- process.exit(1);
102
- }
103
- if (!res.ok) {
104
- const msg = body.error ?? `HTTP ${res.status}`;
105
- console.error(`Error: ${msg}`);
106
- process.exit(1);
107
- }
108
- const { message } = body;
109
- console.log(message);
110
- }
111
82
  export async function listCoworkers(token) {
112
83
  const workers = await fetchWorker(token, "/worker/list-coworkers");
113
84
  console.log(JSON.stringify(workers, null, 2));
@@ -6,7 +6,7 @@ export interface SessionRow {
6
6
  name: string;
7
7
  session_id: string;
8
8
  agent_code: string;
9
- agent: string | null;
9
+ agent: string;
10
10
  status: string | null;
11
11
  created_at: Date;
12
12
  }
@@ -35,9 +35,12 @@ export interface AgenticCodingServer {
35
35
  * Inject a text message into a session (fire-and-forget).
36
36
  * The server processes the message asynchronously.
37
37
  *
38
- * @param agent optional agent-mode identifier to route the prompt.
38
+ * @param agent agent-mode identifier to route the prompt.
39
+ * @param system system prompt appended after the agent's built-in system
40
+ * prompt. Always pass the worker briefing here so the agent
41
+ * has persistent identity/context on every message turn.
39
42
  */
40
- sendMessage(sessionID: string, text: string, agent?: string): Promise<void>;
43
+ sendMessage(sessionID: string, text: string, agent: string, system: string): Promise<void>;
41
44
  /**
42
45
  * Retrieve the message history for a session.
43
46
  *
@@ -4,7 +4,7 @@ export declare class OpenCodeCodingServer implements AgenticCodingServer {
4
4
  constructor(baseURL: string);
5
5
  createSession(): Promise<string>;
6
6
  deleteSession(sessionID: string): Promise<void>;
7
- sendMessage(sessionID: string, text: string, agent?: string): Promise<void>;
7
+ sendMessage(sessionID: string, text: string, agent: string, system: string): Promise<void>;
8
8
  getMessages(sessionID: string, limit?: number): Promise<SessionMessage[]>;
9
9
  revertSession(sessionID: string, messageID: string): Promise<void>;
10
10
  getAgentModes(): Promise<AgentMode[]>;
@@ -18,11 +18,12 @@ export class OpenCodeCodingServer {
18
18
  async deleteSession(sessionID) {
19
19
  await this.client.session.delete({ sessionID });
20
20
  }
21
- async sendMessage(sessionID, text, agent) {
21
+ async sendMessage(sessionID, text, agent, system) {
22
22
  await this.client.session.promptAsync({
23
23
  sessionID,
24
24
  parts: [{ type: "text", text }],
25
- ...(agent ? { agent } : {}),
25
+ agent,
26
+ system,
26
27
  });
27
28
  }
28
29
  async getMessages(sessionID, limit) {
@@ -1,7 +1,8 @@
1
1
  interface CreateSessionProps {
2
2
  serverUrl: string;
3
3
  password: string;
4
+ agent: string;
4
5
  onBack: () => void;
5
6
  }
6
- export declare function CreateSession({ serverUrl, password, onBack }: CreateSessionProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function CreateSession({ serverUrl, password, agent, onBack }: CreateSessionProps): import("react/jsx-runtime").JSX.Element;
7
8
  export {};
@@ -3,7 +3,7 @@ import { useState, useEffect } from "react";
3
3
  import { Box, Text } from "ink";
4
4
  import { TextInput, Spinner } from "@inkjs/ui";
5
5
  import { useApi, useAsyncState } from "../hooks/useApi.js";
6
- export function CreateSession({ serverUrl, password, onBack }) {
6
+ export function CreateSession({ serverUrl, password, agent, onBack }) {
7
7
  const { createSession } = useApi(serverUrl, password);
8
8
  const { loading, error, run } = useAsyncState();
9
9
  const [created, setCreated] = useState(null);
@@ -20,7 +20,7 @@ export function CreateSession({ serverUrl, password, onBack }) {
20
20
  if (!trimmed)
21
21
  return;
22
22
  setSubmitted(true);
23
- const result = await run(() => createSession(trimmed));
23
+ const result = await run(() => createSession(trimmed, agent));
24
24
  if (result)
25
25
  setCreated(result);
26
26
  };
@@ -474,7 +474,7 @@ function MemoryView({ serverUrl, password, sessionName, contentHeight, onClose }
474
474
  }
475
475
  // ─── Main component ──────────────────────────────────────────────────────────
476
476
  export function SessionList({ serverUrl, password, contentHeight, onSubViewChange }) {
477
- const { listSessions, createSession, deleteSession, regenerateCode, getModes, revertToStart, revertAll } = useApi(serverUrl, password);
477
+ const { listSessions, createSession, deleteSession, updateSessionAgent, regenerateCode, getModes, revertToStart, revertAll } = useApi(serverUrl, password);
478
478
  const { data: sessions, loading, error: loadError, run } = useAsyncState();
479
479
  const [cursor, setCursor] = useState(0);
480
480
  const [revealedRows, setRevealedRows] = useState(new Set());
@@ -554,6 +554,26 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
554
554
  setActionMsg(null);
555
555
  setMode("confirm-revert-all");
556
556
  }
557
+ if (input === "a" && rows.length > 0) {
558
+ setActionError(null);
559
+ setActionMsg(null);
560
+ setModeCursor(0);
561
+ setMode("changing-agent-loading");
562
+ getModes().then((modes) => {
563
+ const safeMode = Array.isArray(modes) ? modes : [];
564
+ setAvailableModes(safeMode);
565
+ if (safeMode.length > 0) {
566
+ setMode("changing-agent-pick");
567
+ }
568
+ else {
569
+ setActionError("No agent modes available");
570
+ setMode("create-error");
571
+ }
572
+ }).catch(() => {
573
+ setActionError("Failed to fetch agent modes");
574
+ setMode("create-error");
575
+ });
576
+ }
557
577
  if (rows.length > 0) {
558
578
  if (input === "t")
559
579
  setSubView("tail");
@@ -582,6 +602,23 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
582
602
  }
583
603
  return;
584
604
  }
605
+ if (mode === "changing-agent-pick") {
606
+ const total = availableModes.length;
607
+ if (key.upArrow)
608
+ setModeCursor((c) => (c - 1 + total) % total);
609
+ if (key.downArrow)
610
+ setModeCursor((c) => (c + 1) % total);
611
+ if (key.return) {
612
+ const selected = availableModes[modeCursor]?.name;
613
+ if (selected && rows[cursor]) {
614
+ void handleChangeAgent(rows[cursor].name, selected);
615
+ }
616
+ }
617
+ if (key.escape) {
618
+ setMode("browse");
619
+ }
620
+ return;
621
+ }
585
622
  if (mode === "creating-name" && key.escape) {
586
623
  setMode("browse");
587
624
  return;
@@ -601,9 +638,14 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
601
638
  const trimmed = name.trim();
602
639
  if (!trimmed)
603
640
  return;
641
+ if (!pendingAgent) {
642
+ setActionError("Agent mode is required");
643
+ setMode("create-error");
644
+ return;
645
+ }
604
646
  setMode("creating-busy");
605
647
  try {
606
- await createSession(trimmed, pendingAgent ?? undefined);
648
+ await createSession(trimmed, pendingAgent);
607
649
  const modeNote = pendingAgent ? ` [${pendingAgent}]` : "";
608
650
  setActionMsg(`Coworker "${trimmed}"${modeNote} created.`);
609
651
  setMode("create-done");
@@ -614,6 +656,19 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
614
656
  setMode("create-error");
615
657
  }
616
658
  };
659
+ const handleChangeAgent = async (sessionName, agent) => {
660
+ setMode("changing-agent-busy");
661
+ try {
662
+ await updateSessionAgent(sessionName, agent);
663
+ setActionMsg(`Agent for "${sessionName}" changed to [${agent}].`);
664
+ setMode("create-done");
665
+ reload();
666
+ }
667
+ catch (err) {
668
+ setActionError(err instanceof Error ? err.message : String(err));
669
+ setMode("create-error");
670
+ }
671
+ };
617
672
  const handleConfirmDelete = async (confirmed) => {
618
673
  if (!confirmed) {
619
674
  setMode("browse");
@@ -709,6 +764,20 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
709
764
  setMode("create-error");
710
765
  }
711
766
  };
767
+ // ── Full-screen change-agent flow ──────────────────────────────────────────
768
+ if (mode === "changing-agent-loading") {
769
+ return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Loading agent configs..." }) }));
770
+ }
771
+ if (mode === "changing-agent-pick") {
772
+ const target = rows[cursor];
773
+ return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Change Agent" }), _jsxs(Text, { dimColor: true, children: ["for \"", target?.name, "\""] }), target?.agent ? _jsxs(Text, { color: "yellow", children: ["[current: ", target.agent, "]"] }) : null] }), _jsx(Box, { flexDirection: "column", children: availableModes.map((m, i) => {
774
+ const sel = i === modeCursor;
775
+ return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: sel ? "cyan" : undefined, bold: sel, children: sel ? "▶" : " " }), _jsx(Text, { color: sel ? "cyan" : undefined, bold: sel, children: m.name }), m.description ? _jsxs(Text, { dimColor: true, children: ["\u2014 ", m.description] }) : null] }, i));
776
+ }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc cancel" }) })] }));
777
+ }
778
+ if (mode === "changing-agent-busy") {
779
+ return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Changing agent..." }) }));
780
+ }
712
781
  // ── Full-screen create flow ───────────────────────────────────────────────
713
782
  if (mode === "creating-loading") {
714
783
  return (_jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: _jsx(Spinner, { label: "Loading agent configs..." }) }));
@@ -64,8 +64,9 @@ export interface MemoryRecord {
64
64
  }
65
65
  export declare function useApi(serverUrl: string, password: string): {
66
66
  listSessions: () => Promise<Session[]>;
67
- createSession: (name: string, agent?: string) => Promise<Session>;
67
+ createSession: (name: string, agent: string) => Promise<Session>;
68
68
  deleteSession: (name: string) => Promise<void>;
69
+ updateSessionAgent: (name: string, agent: string) => Promise<Session>;
69
70
  checkHealth: () => Promise<boolean>;
70
71
  getMessages: (name: string, limit?: number) => Promise<SessionMessage[]>;
71
72
  injectText: (name: string, text: string) => Promise<{
@@ -31,7 +31,7 @@ export function useApi(serverUrl, password) {
31
31
  const createSession = useCallback(async (name, agent) => {
32
32
  return apiFetch(`${base}/sessions`, password, {
33
33
  method: "POST",
34
- body: JSON.stringify({ name, ...(agent ? { agent } : {}) }),
34
+ body: JSON.stringify({ name, agent }),
35
35
  });
36
36
  }, [base, password]);
37
37
  const getModes = useCallback(async () => {
@@ -42,6 +42,12 @@ export function useApi(serverUrl, password) {
42
42
  method: "DELETE",
43
43
  });
44
44
  }, [base, password]);
45
+ const updateSessionAgent = useCallback(async (name, agent) => {
46
+ return apiFetch(`${base}/sessions/${encodeURIComponent(name)}/agent`, password, {
47
+ method: "PATCH",
48
+ body: JSON.stringify({ agent }),
49
+ });
50
+ }, [base, password]);
45
51
  const checkHealth = useCallback(async () => {
46
52
  try {
47
53
  await apiFetch(`${base}/health`, password);
@@ -131,6 +137,7 @@ export function useApi(serverUrl, password) {
131
137
  listSessions,
132
138
  createSession,
133
139
  deleteSession,
140
+ updateSessionAgent,
134
141
  checkHealth,
135
142
  getMessages,
136
143
  injectText,
@@ -9,9 +9,10 @@ export declare class CronScheduler {
9
9
  private activeJobs;
10
10
  private sql;
11
11
  private agenticCodingServer;
12
+ private serverUrl;
12
13
  private started;
13
14
  constructor(options?: CronSchedulerOptions);
14
- start(sql: Sql, agenticCodingServer: AgenticCodingServer): Promise<void>;
15
+ start(sql: Sql, agenticCodingServer: AgenticCodingServer, serverUrl: string): Promise<void>;
15
16
  stop(): void;
16
17
  private addJob;
17
18
  private executeJob;
@@ -1,4 +1,5 @@
1
1
  import { Cron } from "croner";
2
+ import { generateSystemPrompt } from "./routes.js";
2
3
  const CRON_INJECTION_BLURB = [
3
4
  ``,
4
5
  `---`,
@@ -10,15 +11,17 @@ export class CronScheduler {
10
11
  activeJobs = new Map();
11
12
  sql = null;
12
13
  agenticCodingServer = null;
14
+ serverUrl = "";
13
15
  started = false;
14
16
  constructor(options = {}) {
15
17
  this.options = options;
16
18
  }
17
- async start(sql, agenticCodingServer) {
19
+ async start(sql, agenticCodingServer, serverUrl) {
18
20
  if (this.started)
19
21
  return;
20
22
  this.sql = sql;
21
23
  this.agenticCodingServer = agenticCodingServer;
24
+ this.serverUrl = serverUrl;
22
25
  const rows = await sql `
23
26
  SELECT id, name, schedule, timezone, message, session_name
24
27
  FROM cron_jobs
@@ -59,13 +62,19 @@ export class CronScheduler {
59
62
  const executedAt = new Date();
60
63
  try {
61
64
  const [session] = await this.sql `
62
- SELECT session_id, agent FROM sessions WHERE name = ${job.session_name}
65
+ SELECT session_id, agent, agent_code, status FROM sessions WHERE name = ${job.session_name}
63
66
  `;
64
67
  if (!session) {
65
68
  throw new Error(`Session "${job.session_name}" not found`);
66
69
  }
70
+ const nameRows = await this.sql `SELECT value FROM config WHERE key = 'human_name'`;
71
+ const descRows = await this.sql `SELECT value FROM config WHERE key = 'human_description'`;
72
+ const humanName = nameRows[0]?.value ?? "your human manager";
73
+ const humanDescription = descRows[0]?.value ?? "";
67
74
  const injectText = `[Cron Job "${job.name}" — ${executedAt.toISOString()}]\n${job.message}${CRON_INJECTION_BLURB}`;
68
- await this.agenticCodingServer.sendMessage(session.session_id, injectText, session.agent ?? undefined);
75
+ const token = `${session.agent_code}@${this.serverUrl}`;
76
+ const system = generateSystemPrompt(job.session_name, session.status, humanName, humanDescription, token);
77
+ await this.agenticCodingServer.sendMessage(session.session_id, injectText, session.agent, system);
69
78
  await this.sql `
70
79
  UPDATE cron_jobs SET last_run = ${executedAt} WHERE id = ${job.id}
71
80
  `;
@@ -3,5 +3,12 @@ import type { Sql } from "../db/index.js";
3
3
  import type { AgenticCodingServer } from "../lib/agentic-coding-server.js";
4
4
  import { CronScheduler } from "./cron.js";
5
5
  import type { MemoryManager } from "./memory.js";
6
+ /**
7
+ * Build the persistent system-prompt briefing for a worker session.
8
+ * This is injected as the `system` field on every `promptAsync` call so the
9
+ * agent always has its identity, token, and command reference in context —
10
+ * without consuming a user-message turn.
11
+ */
12
+ export declare function generateSystemPrompt(name: string, status: string | null, humanName: string, humanDescription: string, token: string): string;
6
13
  export declare function createRouter(sql: Sql, agenticCodingServer: AgenticCodingServer, serverUrl: string, scheduler: CronScheduler, memoryManager: MemoryManager): Router;
7
14
  export declare function createWorkerRouter(sql: Sql, agenticCodingServer: AgenticCodingServer, serverUrl: string, memoryManager: MemoryManager): Router;
@@ -16,16 +16,19 @@ const MAIL_INJECTION_BLURB = [
16
16
  `Tip: For currency or prices, use code blocks. Example: put numbers in single or`,
17
17
  `double quotes to preserve formatting characters like dollar signs.`,
18
18
  ].join("\n");
19
- function generateEnrollmentMessage(token) {
20
- return `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 ${token}`;
21
- }
22
- function generateWelcomeMessage(name, agent, status, humanName, humanDescription, token) {
19
+ /**
20
+ * Build the persistent system-prompt briefing for a worker session.
21
+ * This is injected as the `system` field on every `promptAsync` call so the
22
+ * agent always has its identity, token, and command reference in context —
23
+ * without consuming a user-message turn.
24
+ */
25
+ export function generateSystemPrompt(name, status, humanName, humanDescription, token) {
23
26
  return [
24
27
  `╔══════════════════════════════════════════════════════╗`,
25
28
  `║ WELCOME TO THE AGENT OFFICE ║`,
26
29
  `╚══════════════════════════════════════════════════════╝`,
27
30
  ``,
28
- `You are now clocked in.`,
31
+ `You are an AI worker agent enrolled in the agent office.`,
29
32
  ` Name: ${name}`,
30
33
  ...(status ? [` Status: ${status}`] : []),
31
34
  ` Human manager: ${humanName} — the human who created your`,
@@ -94,7 +97,7 @@ function generateWelcomeMessage(name, agent, status, humanName, humanDescription
94
97
  ` ${token} <memory-id>`,
95
98
  ``,
96
99
  `════════════════════════════════════════════════════════`,
97
- ` IMPORTANT: YOUR SESSIONS ARE PRIVATE`,
100
+ ` IMPORTANT: YOUR SESSIONS ARE PRIVATE`,
98
101
  `════════════════════════════════════════════════════════`,
99
102
  ``,
100
103
  ` Nobody — not ${humanName}, not your coworkers — can see`,
@@ -129,6 +132,15 @@ function generateWelcomeMessage(name, agent, status, humanName, humanDescription
129
132
  ``,
130
133
  ].join("\n");
131
134
  }
135
+ /** Load human_name and human_description from the config table. */
136
+ async function loadHumanConfig(sql) {
137
+ const nameRows = await sql `SELECT value FROM config WHERE key = 'human_name'`;
138
+ const descRows = await sql `SELECT value FROM config WHERE key = 'human_description'`;
139
+ return {
140
+ humanName: nameRows[0]?.value ?? "your human manager",
141
+ humanDescription: descRows[0]?.value ?? "",
142
+ };
143
+ }
132
144
  export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, memoryManager) {
133
145
  const router = Router();
134
146
  router.get("/health", (_req, res) => {
@@ -164,8 +176,12 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
164
176
  res.status(400).json({ error: "name is required" });
165
177
  return;
166
178
  }
179
+ if (!agentArg || typeof agentArg !== "string" || !agentArg.trim()) {
180
+ res.status(400).json({ error: "agent is required" });
181
+ return;
182
+ }
167
183
  const trimmedName = name.trim();
168
- const trimmedAgent = typeof agentArg === "string" && agentArg.trim() ? agentArg.trim() : null;
184
+ const trimmedAgent = agentArg.trim();
169
185
  const existing = await sql `
170
186
  SELECT id FROM sessions WHERE name = ${trimmedName}
171
187
  `;
@@ -201,9 +217,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
201
217
  return;
202
218
  }
203
219
  try {
204
- const clockInToken = `${row.agent_code}@${serverUrl}`;
205
- const enrollmentMessage = generateEnrollmentMessage(clockInToken);
206
- await agenticCodingServer.sendMessage(opencodeSessionId, enrollmentMessage, trimmedAgent ?? undefined);
220
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
221
+ const token = `${row.agent_code}@${serverUrl}`;
222
+ const system = generateSystemPrompt(trimmedName, null, humanName, humanDescription, token);
223
+ await agenticCodingServer.sendMessage(opencodeSessionId, "You are now active. Begin your work.", trimmedAgent, system);
207
224
  }
208
225
  catch (err) {
209
226
  console.warn("Warning: could not send first message to session:", err);
@@ -233,6 +250,35 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
233
250
  res.status(500).json({ error: "Internal server error" });
234
251
  }
235
252
  });
253
+ router.patch("/sessions/:name/agent", async (req, res) => {
254
+ const { name } = req.params;
255
+ const { agent } = req.body;
256
+ if (!agent || typeof agent !== "string" || !agent.trim()) {
257
+ res.status(400).json({ error: "agent is required" });
258
+ return;
259
+ }
260
+ const trimmedAgent = agent.trim();
261
+ const rows = await sql `
262
+ SELECT id FROM sessions WHERE name = ${name}
263
+ `;
264
+ if (rows.length === 0) {
265
+ res.status(404).json({ error: `Session "${name}" not found` });
266
+ return;
267
+ }
268
+ try {
269
+ const [updated] = await sql `
270
+ UPDATE sessions
271
+ SET agent = ${trimmedAgent}
272
+ WHERE name = ${name}
273
+ RETURNING id, name, session_id, agent_code, agent, created_at
274
+ `;
275
+ res.json(updated);
276
+ }
277
+ catch (err) {
278
+ console.error("PATCH /sessions/:name/agent error:", err);
279
+ res.status(500).json({ error: "Internal server error" });
280
+ }
281
+ });
236
282
  router.get("/sessions/:name/messages", async (req, res) => {
237
283
  const { name } = req.params;
238
284
  const limit = Math.min(parseInt(req.query.limit ?? "20", 10), 100);
@@ -286,7 +332,7 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
286
332
  return;
287
333
  }
288
334
  const rows = await sql `
289
- SELECT id, name, session_id, agent_code, created_at FROM sessions WHERE name = ${name}
335
+ SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions WHERE name = ${name}
290
336
  `;
291
337
  if (rows.length === 0) {
292
338
  res.status(404).json({ error: `Session "${name}" not found` });
@@ -294,7 +340,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
294
340
  }
295
341
  const row = rows[0];
296
342
  try {
297
- await agenticCodingServer.sendMessage(row.session_id, text.trim());
343
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
344
+ const token = `${row.agent_code}@${serverUrl}`;
345
+ const system = generateSystemPrompt(row.name, row.status ?? null, humanName, humanDescription, token);
346
+ await agenticCodingServer.sendMessage(row.session_id, text.trim(), row.agent, system);
298
347
  res.json({ ok: true });
299
348
  }
300
349
  catch (err) {
@@ -324,9 +373,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
324
373
  return;
325
374
  }
326
375
  await agenticCodingServer.revertSession(session.session_id, firstMessage.id);
327
- const clockInToken = `${session.agent_code}@${serverUrl}`;
328
- const enrollmentMessage = generateEnrollmentMessage(clockInToken);
329
- await agenticCodingServer.sendMessage(session.session_id, enrollmentMessage, session.agent ?? undefined);
376
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
377
+ const token = `${session.agent_code}@${serverUrl}`;
378
+ const system = generateSystemPrompt(session.name, session.status ?? null, humanName, humanDescription, token);
379
+ await agenticCodingServer.sendMessage(session.session_id, "You are now active. Begin your work.", session.agent ?? undefined, system);
330
380
  res.json({ ok: true, messageID: firstMessage.id });
331
381
  }
332
382
  catch (err) {
@@ -339,6 +389,7 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
339
389
  SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions
340
390
  `;
341
391
  const results = [];
392
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
342
393
  for (const session of allSessions) {
343
394
  try {
344
395
  const messages = await agenticCodingServer.getMessages(session.session_id);
@@ -352,9 +403,9 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
352
403
  continue;
353
404
  }
354
405
  await agenticCodingServer.revertSession(session.session_id, firstMessage.id);
355
- const clockInToken = `${session.agent_code}@${serverUrl}`;
356
- const enrollmentMessage = generateEnrollmentMessage(clockInToken);
357
- await agenticCodingServer.sendMessage(session.session_id, enrollmentMessage, session.agent ?? undefined);
406
+ const token = `${session.agent_code}@${serverUrl}`;
407
+ const system = generateSystemPrompt(session.name, session.status ?? null, humanName, humanDescription, token);
408
+ await agenticCodingServer.sendMessage(session.session_id, "You are now active. Begin your work.", session.agent ?? undefined, system);
358
409
  results.push({ name: session.name, ok: true });
359
410
  }
360
411
  catch (err) {
@@ -482,15 +533,14 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
482
533
  }
483
534
  const trimmedFrom = from.trim();
484
535
  const trimmedBody = body.trim();
485
- const sessions = await sql `SELECT name, session_id FROM sessions`;
486
- const sessionMap = new Map(sessions.map((s) => [s.name, s.session_id]));
536
+ const sessions = await sql `SELECT name, session_id, agent, agent_code, status FROM sessions`;
537
+ const sessionMap = new Map(sessions.map((s) => [s.name, { sessionId: s.session_id, agent: s.agent, agentCode: s.agent_code, status: s.status }]));
538
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
487
539
  const validRecipients = [];
488
540
  for (const recipient of to) {
489
541
  if (typeof recipient !== "string" || !recipient.trim())
490
542
  continue;
491
543
  const r = recipient.trim();
492
- const config = await sql `SELECT value FROM config WHERE key = 'human_name'`;
493
- const humanName = config[0]?.value ?? "Human";
494
544
  if (sessionMap.has(r) || r === humanName) {
495
545
  validRecipients.push(r);
496
546
  }
@@ -509,10 +559,12 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
509
559
  const msgId = msgRow.id;
510
560
  let injected = false;
511
561
  if (sessionMap.has(recipient)) {
512
- const sessionId = sessionMap.get(recipient);
562
+ const { sessionId, agent, agentCode, status } = sessionMap.get(recipient);
513
563
  const injectText = `[Message from "${trimmedFrom}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
564
+ const token = `${agentCode}@${serverUrl}`;
565
+ const system = generateSystemPrompt(recipient, status ?? null, humanName, humanDescription, token);
514
566
  try {
515
- await agenticCodingServer.sendMessage(sessionId, injectText);
567
+ await agenticCodingServer.sendMessage(sessionId, injectText, agent ?? undefined, system);
516
568
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
517
569
  injected = true;
518
570
  }
@@ -923,35 +975,6 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
923
975
  }
924
976
  export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryManager) {
925
977
  const router = Router();
926
- router.get("/worker/clock-in", async (req, res) => {
927
- const { code } = req.query;
928
- if (!code || typeof code !== "string") {
929
- res.status(400).json({ error: "code query parameter is required" });
930
- return;
931
- }
932
- const rows = await sql `
933
- SELECT id, name, session_id, agent_code, created_at
934
- FROM sessions
935
- WHERE agent_code = ${code}
936
- `;
937
- if (rows.length === 0) {
938
- res.status(401).json({ error: "Invalid agent code" });
939
- return;
940
- }
941
- const session = rows[0];
942
- const token = `${session.agent_code}@<server-url>`;
943
- const humanConfig = await sql `SELECT value FROM config WHERE key = 'human_name'`;
944
- const humanName = humanConfig[0]?.value ?? "your human manager";
945
- const humanDescConfig = await sql `SELECT value FROM config WHERE key = 'human_description'`;
946
- const humanDescription = humanDescConfig[0]?.value ?? "";
947
- const message = generateWelcomeMessage(session.name, session.agent, session.status ?? null, humanName, humanDescription, token);
948
- res.json({
949
- ok: true,
950
- name: session.name,
951
- session_id: session.session_id,
952
- message,
953
- });
954
- });
955
978
  router.get("/worker/list-coworkers", async (req, res) => {
956
979
  const { code } = req.query;
957
980
  if (!code || typeof code !== "string") {
@@ -1043,10 +1066,9 @@ export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryMa
1043
1066
  return;
1044
1067
  }
1045
1068
  const trimmedBody = body.trim();
1046
- const sessions = await sql `SELECT name, session_id FROM sessions`;
1047
- const sessionMap = new Map(sessions.map((s) => [s.name, s.session_id]));
1048
- const config = await sql `SELECT value FROM config WHERE key = 'human_name'`;
1049
- const humanName = config[0]?.value ?? "Human";
1069
+ const sessions = await sql `SELECT name, session_id, agent, agent_code, status FROM sessions`;
1070
+ const sessionMap = new Map(sessions.map((s) => [s.name, { sessionId: s.session_id, agent: s.agent, agentCode: s.agent_code, status: s.status }]));
1071
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
1050
1072
  const validRecipients = [];
1051
1073
  for (const recipient of to) {
1052
1074
  if (typeof recipient !== "string" || !recipient.trim())
@@ -1070,10 +1092,12 @@ export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryMa
1070
1092
  const msgId = msgRow.id;
1071
1093
  let injected = false;
1072
1094
  if (sessionMap.has(recipient)) {
1073
- const recipientSessionId = sessionMap.get(recipient);
1095
+ const { sessionId: recipientSessionId, agent: recipientAgent, agentCode, status } = sessionMap.get(recipient);
1074
1096
  const injectText = `[Message from "${session.name}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
1097
+ const token = `${agentCode}@${serverUrl}`;
1098
+ const system = generateSystemPrompt(recipient, status ?? null, humanName, humanDescription, token);
1075
1099
  try {
1076
- await agenticCodingServer.sendMessage(recipientSessionId, injectText);
1100
+ await agenticCodingServer.sendMessage(recipientSessionId, injectText, recipientAgent ?? undefined, system);
1077
1101
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
1078
1102
  injected = true;
1079
1103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-office",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "An office for your AI agents",
5
5
  "type": "module",
6
6
  "license": "MIT",