agent-office 0.0.15 → 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));
@@ -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) => {
@@ -205,9 +217,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
205
217
  return;
206
218
  }
207
219
  try {
208
- const clockInToken = `${row.agent_code}@${serverUrl}`;
209
- const enrollmentMessage = generateEnrollmentMessage(clockInToken);
210
- await agenticCodingServer.sendMessage(opencodeSessionId, enrollmentMessage, trimmedAgent);
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);
211
224
  }
212
225
  catch (err) {
213
226
  console.warn("Warning: could not send first message to session:", err);
@@ -319,7 +332,7 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
319
332
  return;
320
333
  }
321
334
  const rows = await sql `
322
- SELECT id, name, session_id, agent_code, agent, created_at FROM sessions WHERE name = ${name}
335
+ SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions WHERE name = ${name}
323
336
  `;
324
337
  if (rows.length === 0) {
325
338
  res.status(404).json({ error: `Session "${name}" not found` });
@@ -327,7 +340,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
327
340
  }
328
341
  const row = rows[0];
329
342
  try {
330
- await agenticCodingServer.sendMessage(row.session_id, text.trim(), row.agent);
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);
331
347
  res.json({ ok: true });
332
348
  }
333
349
  catch (err) {
@@ -357,9 +373,10 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
357
373
  return;
358
374
  }
359
375
  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);
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);
363
380
  res.json({ ok: true, messageID: firstMessage.id });
364
381
  }
365
382
  catch (err) {
@@ -372,6 +389,7 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
372
389
  SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions
373
390
  `;
374
391
  const results = [];
392
+ const { humanName, humanDescription } = await loadHumanConfig(sql);
375
393
  for (const session of allSessions) {
376
394
  try {
377
395
  const messages = await agenticCodingServer.getMessages(session.session_id);
@@ -385,9 +403,9 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
385
403
  continue;
386
404
  }
387
405
  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);
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);
391
409
  results.push({ name: session.name, ok: true });
392
410
  }
393
411
  catch (err) {
@@ -515,15 +533,14 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
515
533
  }
516
534
  const trimmedFrom = from.trim();
517
535
  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 }]));
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);
520
539
  const validRecipients = [];
521
540
  for (const recipient of to) {
522
541
  if (typeof recipient !== "string" || !recipient.trim())
523
542
  continue;
524
543
  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
544
  if (sessionMap.has(r) || r === humanName) {
528
545
  validRecipients.push(r);
529
546
  }
@@ -542,10 +559,12 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
542
559
  const msgId = msgRow.id;
543
560
  let injected = false;
544
561
  if (sessionMap.has(recipient)) {
545
- const { sessionId, agent } = sessionMap.get(recipient);
562
+ const { sessionId, agent, agentCode, status } = sessionMap.get(recipient);
546
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);
547
566
  try {
548
- await agenticCodingServer.sendMessage(sessionId, injectText, agent ?? undefined);
567
+ await agenticCodingServer.sendMessage(sessionId, injectText, agent ?? undefined, system);
549
568
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
550
569
  injected = true;
551
570
  }
@@ -956,35 +975,6 @@ export function createRouter(sql, agenticCodingServer, serverUrl, scheduler, mem
956
975
  }
957
976
  export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryManager) {
958
977
  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
978
  router.get("/worker/list-coworkers", async (req, res) => {
989
979
  const { code } = req.query;
990
980
  if (!code || typeof code !== "string") {
@@ -1076,10 +1066,9 @@ export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryMa
1076
1066
  return;
1077
1067
  }
1078
1068
  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";
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);
1083
1072
  const validRecipients = [];
1084
1073
  for (const recipient of to) {
1085
1074
  if (typeof recipient !== "string" || !recipient.trim())
@@ -1103,10 +1092,12 @@ export function createWorkerRouter(sql, agenticCodingServer, serverUrl, memoryMa
1103
1092
  const msgId = msgRow.id;
1104
1093
  let injected = false;
1105
1094
  if (sessionMap.has(recipient)) {
1106
- const { sessionId: recipientSessionId, agent: recipientAgent } = sessionMap.get(recipient);
1095
+ const { sessionId: recipientSessionId, agent: recipientAgent, agentCode, status } = sessionMap.get(recipient);
1107
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);
1108
1099
  try {
1109
- await agenticCodingServer.sendMessage(recipientSessionId, injectText, recipientAgent ?? undefined);
1100
+ await agenticCodingServer.sendMessage(recipientSessionId, injectText, recipientAgent ?? undefined, system);
1110
1101
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
1111
1102
  injected = true;
1112
1103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-office",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "An office for your AI agents",
5
5
  "type": "module",
6
6
  "license": "MIT",