agent-office 0.0.15 → 0.2.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/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));
@@ -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 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
25
  agent,
26
+ system,
26
27
  });
27
28
  }
28
29
  async getMessages(sessionID, limit) {
@@ -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);
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) => {
@@ -204,14 +216,6 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
204
216
  res.status(500).json({ error: "Internal server error" });
205
217
  return;
206
218
  }
207
- try {
208
- const clockInToken = `${row.agent_code}@${serverUrl}`;
209
- const enrollmentMessage = generateEnrollmentMessage(clockInToken);
210
- await agenticCodingServer.sendMessage(opencodeSessionId, enrollmentMessage, trimmedAgent);
211
- }
212
- catch (err) {
213
- console.warn("Warning: could not send first message to session:", err);
214
- }
215
219
  res.status(201).json(row);
216
220
  });
217
221
  router.post("/sessions/:name/regenerate-code", async (req, res) => {
@@ -319,7 +323,7 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
319
323
  return;
320
324
  }
321
325
  const rows = await sql `
322
- SELECT id, name, session_id, agent_code, agent, created_at FROM sessions WHERE name = ${name}
326
+ SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions WHERE name = ${name}
323
327
  `;
324
328
  if (rows.length === 0) {
325
329
  res.status(404).json({ error: `Session "${name}" not found` });
@@ -327,7 +331,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
327
331
  }
328
332
  const row = rows[0];
329
333
  try {
330
- await agenticCodingServer.sendMessage(row.session_id, text.trim(), row.agent);
334
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
335
+ const token = `${row.agent_code}@${serverUrl}`;
336
+ const system = generateSystemPrompt(row.name, row.status ?? null, humanName, humanDescription, token);
337
+ await agenticCodingServer.sendMessage(row.session_id, text.trim(), row.agent, system);
331
338
  res.json({ ok: true });
332
339
  }
333
340
  catch (err) {
@@ -357,9 +364,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
357
364
  return;
358
365
  }
359
366
  await agenticCodingServer.revertSession(session.session_id, firstMessage.id);
360
- const clockInToken = `${session.agent_code}@${serverUrl}`;
361
- const enrollmentMessage = generateEnrollmentMessage(clockInToken);
362
- await agenticCodingServer.sendMessage(session.session_id, enrollmentMessage, session.agent ?? undefined);
367
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
368
+ const token = `${session.agent_code}@${serverUrl}`;
369
+ const system = generateSystemPrompt(session.name, session.status ?? null, humanName, humanDescription, token);
370
+ await agenticCodingServer.sendMessage(session.session_id, "You are now active. Begin your work.", session.agent ?? undefined, system);
363
371
  res.json({ ok: true, messageID: firstMessage.id });
364
372
  }
365
373
  catch (err) {
@@ -372,6 +380,7 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
372
380
  SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions
373
381
  `;
374
382
  const results = [];
383
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
375
384
  for (const session of allSessions) {
376
385
  try {
377
386
  const messages = await agenticCodingServer.getMessages(session.session_id);
@@ -385,9 +394,9 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
385
394
  continue;
386
395
  }
387
396
  await agenticCodingServer.revertSession(session.session_id, firstMessage.id);
388
- const clockInToken = `${session.agent_code}@${serverUrl}`;
389
- const enrollmentMessage = generateEnrollmentMessage(clockInToken);
390
- await agenticCodingServer.sendMessage(session.session_id, enrollmentMessage, session.agent ?? undefined);
397
+ const token = `${session.agent_code}@${serverUrl}`;
398
+ const system = generateSystemPrompt(session.name, session.status ?? null, humanName, humanDescription, token);
399
+ await agenticCodingServer.sendMessage(session.session_id, "You are now active. Begin your work.", session.agent ?? undefined, system);
391
400
  results.push({ name: session.name, ok: true });
392
401
  }
393
402
  catch (err) {
@@ -515,15 +524,14 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
515
524
  }
516
525
  const trimmedFrom = from.trim();
517
526
  const trimmedBody = body.trim();
518
- const sessions = await sql `SELECT name, session_id, agent FROM sessions`;
519
- const sessionMap = new Map(sessions.map((s) => [s.name, { sessionId: s.session_id, agent: s.agent }]));
527
+ const sessions = await sql `SELECT name, session_id, agent, agent_code, status FROM sessions`;
528
+ const sessionMap = new Map(sessions.map((s) => [s.name, { sessionId: s.session_id, agent: s.agent, agentCode: s.agent_code, status: s.status }]));
529
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
520
530
  const validRecipients = [];
521
531
  for (const recipient of to) {
522
532
  if (typeof recipient !== "string" || !recipient.trim())
523
533
  continue;
524
534
  const r = recipient.trim();
525
- const config = await sql `SELECT value FROM config WHERE key = 'human_name'`;
526
- const humanName = config[0]?.value ?? "Human";
527
535
  if (sessionMap.has(r) || r === humanName) {
528
536
  validRecipients.push(r);
529
537
  }
@@ -542,10 +550,12 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
542
550
  const msgId = msgRow.id;
543
551
  let injected = false;
544
552
  if (sessionMap.has(recipient)) {
545
- const { sessionId, agent } = sessionMap.get(recipient);
553
+ const { sessionId, agent, agentCode, status } = sessionMap.get(recipient);
546
554
  const injectText = `[Message from "${trimmedFrom}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
555
+ const token = `${agentCode}@${serverUrl}`;
556
+ const system = generateSystemPrompt(recipient, status ?? null, humanName, humanDescription, token);
547
557
  try {
548
- await agenticCodingServer.sendMessage(sessionId, injectText, agent ?? undefined);
558
+ await agenticCodingServer.sendMessage(sessionId, injectText, agent ?? undefined, system);
549
559
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
550
560
  injected = true;
551
561
  }
@@ -956,35 +966,6 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
956
966
  }
957
967
  export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryManager) {
958
968
  const router = Router();
959
- router.get("/worker/clock-in", async (req, res) => {
960
- const { code } = req.query;
961
- if (!code || typeof code !== "string") {
962
- res.status(400).json({ error: "code query parameter is required" });
963
- return;
964
- }
965
- const rows = await sql `
966
- SELECT id, name, session_id, agent_code, created_at
967
- FROM sessions
968
- WHERE agent_code = ${code}
969
- `;
970
- if (rows.length === 0) {
971
- res.status(401).json({ error: "Invalid agent code" });
972
- return;
973
- }
974
- const session = rows[0];
975
- const token = `${session.agent_code}@<server-url>`;
976
- const humanConfig = await sql `SELECT value FROM config WHERE key = 'human_name'`;
977
- const humanName = humanConfig[0]?.value ?? "your human manager";
978
- const humanDescConfig = await sql `SELECT value FROM config WHERE key = 'human_description'`;
979
- const humanDescription = humanDescConfig[0]?.value ?? "";
980
- const message = generateWelcomeMessage(session.name, session.agent, session.status ?? null, humanName, humanDescription, token);
981
- res.json({
982
- ok: true,
983
- name: session.name,
984
- session_id: session.session_id,
985
- message,
986
- });
987
- });
988
969
  router.get("/worker/list-coworkers", async (req, res) => {
989
970
  const { code } = req.query;
990
971
  if (!code || typeof code !== "string") {
@@ -1076,10 +1057,9 @@ export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryMa
1076
1057
  return;
1077
1058
  }
1078
1059
  const trimmedBody = body.trim();
1079
- const sessions = await sql `SELECT name, session_id, agent FROM sessions`;
1080
- const sessionMap = new Map(sessions.map((s) => [s.name, { sessionId: s.session_id, agent: s.agent }]));
1081
- const config = await sql `SELECT value FROM config WHERE key = 'human_name'`;
1082
- const humanName = config[0]?.value ?? "Human";
1060
+ const sessions = await sql `SELECT name, session_id, agent, agent_code, status FROM sessions`;
1061
+ const sessionMap = new Map(sessions.map((s) => [s.name, { sessionId: s.session_id, agent: s.agent, agentCode: s.agent_code, status: s.status }]));
1062
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
1083
1063
  const validRecipients = [];
1084
1064
  for (const recipient of to) {
1085
1065
  if (typeof recipient !== "string" || !recipient.trim())
@@ -1103,10 +1083,12 @@ export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryMa
1103
1083
  const msgId = msgRow.id;
1104
1084
  let injected = false;
1105
1085
  if (sessionMap.has(recipient)) {
1106
- const { sessionId: recipientSessionId, agent: recipientAgent } = sessionMap.get(recipient);
1086
+ const { sessionId: recipientSessionId, agent: recipientAgent, agentCode, status } = sessionMap.get(recipient);
1107
1087
  const injectText = `[Message from "${session.name}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
1088
+ const token = `${agentCode}@${serverUrl}`;
1089
+ const system = generateSystemPrompt(recipient, status ?? null, humanName, humanDescription, token);
1108
1090
  try {
1109
- await agenticCodingServer.sendMessage(recipientSessionId, injectText, recipientAgent ?? undefined);
1091
+ await agenticCodingServer.sendMessage(recipientSessionId, injectText, recipientAgent ?? undefined, system);
1110
1092
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
1111
1093
  injected = true;
1112
1094
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-office",
3
- "version": "0.0.15",
3
+ "version": "0.2.0",
4
4
  "description": "An office for your AI agents",
5
5
  "type": "module",
6
6
  "license": "MIT",