agent-office 0.0.9 → 0.0.10

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.
@@ -6,7 +6,7 @@ export interface SessionRow {
6
6
  name: string;
7
7
  session_id: string;
8
8
  agent_code: string;
9
- mode: string | null;
9
+ agent: string | null;
10
10
  status: string | null;
11
11
  created_at: Date;
12
12
  }
@@ -93,6 +93,13 @@ const MIGRATIONS = [
93
93
  ALTER TABLE sessions ADD COLUMN IF NOT EXISTS status TEXT NULL;
94
94
  `,
95
95
  },
96
+ {
97
+ version: 8,
98
+ name: "rename_mode_to_agent",
99
+ sql: `
100
+ ALTER TABLE sessions RENAME COLUMN mode TO agent;
101
+ `,
102
+ },
96
103
  ];
97
104
  export async function runMigrations(sql) {
98
105
  await sql `
@@ -1,5 +1,5 @@
1
- import Opencode from "@opencode-ai/sdk";
2
- export type OpencodeClient = InstanceType<typeof Opencode>;
1
+ import { createOpencodeClient as _createOpencodeClient } from "@opencode-ai/sdk/v2/client";
2
+ export type OpencodeClient = ReturnType<typeof _createOpencodeClient>;
3
3
  export declare function createOpencodeClient(baseURL: string): OpencodeClient;
4
4
  export interface OpencodeSession {
5
5
  id: string;
@@ -1,4 +1,5 @@
1
- import Opencode from "@opencode-ai/sdk";
1
+ import { createOpencodeClient as _createOpencodeClient } from "@opencode-ai/sdk/v2/client";
2
+ import { cwd } from "process";
2
3
  export function createOpencodeClient(baseURL) {
3
- return new Opencode({ baseURL });
4
+ return _createOpencodeClient({ baseUrl: baseURL, directory: cwd() });
4
5
  }
@@ -483,7 +483,7 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
483
483
  const [actionError, setActionError] = useState(null);
484
484
  const [actionMsg, setActionMsg] = useState(null);
485
485
  const [availableModes, setAvailableModes] = useState([]);
486
- const [pendingMode, setPendingMode] = useState(null);
486
+ const [pendingAgent, setPendingAgent] = useState(null);
487
487
  const [modeCursor, setModeCursor] = useState(0);
488
488
  const reload = () => void run(listSessions);
489
489
  useEffect(() => { reload(); }, []);
@@ -522,7 +522,7 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
522
522
  if (input === "c") {
523
523
  setActionError(null);
524
524
  setActionMsg(null);
525
- setPendingMode(null);
525
+ setPendingAgent(null);
526
526
  setModeCursor(0);
527
527
  setMode("creating-loading");
528
528
  getModes().then((modes) => {
@@ -574,7 +574,7 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
574
574
  setModeCursor((c) => (c + 1) % total);
575
575
  if (key.return) {
576
576
  const selected = modeCursor === 0 ? null : (availableModes[modeCursor - 1]?.name ?? null);
577
- setPendingMode(selected);
577
+ setPendingAgent(selected);
578
578
  setMode("creating-name");
579
579
  }
580
580
  if (key.escape) {
@@ -603,8 +603,8 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
603
603
  return;
604
604
  setMode("creating-busy");
605
605
  try {
606
- await createSession(trimmed, pendingMode ?? undefined);
607
- const modeNote = pendingMode ? ` [${pendingMode}]` : "";
606
+ await createSession(trimmed, pendingAgent ?? undefined);
607
+ const modeNote = pendingAgent ? ` [${pendingAgent}]` : "";
608
608
  setActionMsg(`Coworker "${trimmed}"${modeNote} created.`);
609
609
  setMode("create-done");
610
610
  reload();
@@ -724,8 +724,8 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
724
724
  }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc cancel" }) })] }));
725
725
  }
726
726
  if (mode === "creating-name") {
727
- return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, children: "Create Coworker" }), pendingMode
728
- ? _jsxs(Text, { color: "yellow", children: ["[", pendingMode, "]"] })
727
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, children: "Create Coworker" }), pendingAgent
728
+ ? _jsxs(Text, { color: "yellow", children: ["[", pendingAgent, "]"] })
729
729
  : _jsx(Text, { dimColor: true, children: "[no mode]" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { placeholder: "e.g. alice", onSubmit: (v) => void handleCreate(v) })] }), _jsx(Text, { dimColor: true, children: "Enter to create \u00B7 Esc cancel" })] }));
730
730
  }
731
731
  if (mode === "creating-busy") {
@@ -793,7 +793,7 @@ export function SessionList({ serverUrl, password, contentHeight, onSubViewChang
793
793
  return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Coworkers" }), _jsxs(Text, { dimColor: true, children: ["(", rows.length, ")"] }), loading && _jsx(Spinner, {})] }), actionPanel, rows.length === 0 ? (_jsx(Box, { height: tableHeight, alignItems: "center", justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "No coworkers yet. Press c to create one." }) })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: " NAME".padEnd(18) }), _jsx(Text, { bold: true, color: "cyan", children: "STATUS".padEnd(20) }), _jsx(Text, { bold: true, color: "cyan", children: "MODE".padEnd(12) }), _jsx(Text, { bold: true, color: "cyan", children: "OPENCODE SESSION ID".padEnd(36) }), _jsx(Text, { bold: true, color: "cyan", children: "AGENT CODE" })] }), rows.map((s, i) => {
794
794
  const selected = i === cursor;
795
795
  const revealed = revealedRows.has(s.id);
796
- return (_jsxs(Box, { gap: 2, children: [_jsxs(Box, { width: 18, children: [_jsx(Text, { color: selected ? "cyan" : undefined, children: selected ? "▶ " : " " }), _jsx(Text, { color: selected ? "cyan" : "green", bold: selected, children: s.name })] }), _jsx(Text, { color: selected ? "cyan" : undefined, dimColor: !selected && !s.status, children: (s.status ?? "—").padEnd(20) }), _jsx(Text, { color: selected ? "magenta" : undefined, dimColor: !selected && !s.mode, children: (s.mode ?? "—").padEnd(12) }), _jsx(Text, { dimColor: !selected, children: s.session_id.padEnd(36) }), revealed
796
+ return (_jsxs(Box, { gap: 2, children: [_jsxs(Box, { width: 18, children: [_jsx(Text, { color: selected ? "cyan" : undefined, children: selected ? "▶ " : " " }), _jsx(Text, { color: selected ? "cyan" : "green", bold: selected, children: s.name })] }), _jsx(Text, { color: selected ? "cyan" : undefined, dimColor: !selected && !s.status, children: (s.status ?? "—").padEnd(20) }), _jsx(Text, { color: selected ? "magenta" : undefined, dimColor: !selected && !s.agent, children: (s.agent ?? "—").padEnd(12) }), _jsx(Text, { dimColor: !selected, children: s.session_id.padEnd(36) }), revealed
797
797
  ? _jsx(Text, { color: "yellow", children: s.agent_code })
798
798
  : _jsx(Text, { dimColor: true, children: MASKED_CODE })] }, s.id));
799
799
  })] }))] }));
@@ -3,7 +3,7 @@ export interface Session {
3
3
  name: string;
4
4
  session_id: string;
5
5
  agent_code: string;
6
- mode: string | null;
6
+ agent: string | null;
7
7
  status: string | null;
8
8
  created_at: string;
9
9
  }
@@ -64,7 +64,7 @@ export interface MemoryRecord {
64
64
  }
65
65
  export declare function useApi(serverUrl: string, password: string): {
66
66
  listSessions: () => Promise<Session[]>;
67
- createSession: (name: string, mode?: string) => Promise<Session>;
67
+ createSession: (name: string, agent?: string) => Promise<Session>;
68
68
  deleteSession: (name: string) => Promise<void>;
69
69
  checkHealth: () => Promise<boolean>;
70
70
  getMessages: (name: string, limit?: number) => Promise<SessionMessage[]>;
@@ -28,10 +28,10 @@ export function useApi(serverUrl, password) {
28
28
  const listSessions = useCallback(async () => {
29
29
  return apiFetch(`${base}/sessions`, password);
30
30
  }, [base, password]);
31
- const createSession = useCallback(async (name, mode) => {
31
+ const createSession = useCallback(async (name, agent) => {
32
32
  return apiFetch(`${base}/sessions`, password, {
33
33
  method: "POST",
34
- body: JSON.stringify({ name, ...(mode ? { mode } : {}) }),
34
+ body: JSON.stringify({ name, ...(agent ? { agent } : {}) }),
35
35
  });
36
36
  }, [base, password]);
37
37
  const getModes = useCallback(async () => {
@@ -59,21 +59,16 @@ export class CronScheduler {
59
59
  const executedAt = new Date();
60
60
  try {
61
61
  const [session] = await this.sql `
62
- SELECT session_id FROM sessions WHERE name = ${job.session_name}
62
+ SELECT session_id, agent FROM sessions WHERE name = ${job.session_name}
63
63
  `;
64
64
  if (!session) {
65
65
  throw new Error(`Session "${job.session_name}" not found`);
66
66
  }
67
- const providers = await this.opencode.app.providers();
68
- const defaultEntry = Object.entries(providers.default)[0];
69
- if (!defaultEntry) {
70
- throw new Error("No default model configured");
71
- }
72
67
  const injectText = `[Cron Job "${job.name}" — ${executedAt.toISOString()}]\n${job.message}${CRON_INJECTION_BLURB}`;
73
- await this.opencode.session.chat(session.session_id, {
74
- modelID: defaultEntry[0],
75
- providerID: defaultEntry[1],
68
+ await this.opencode.session.promptAsync({
69
+ sessionID: session.session_id,
76
70
  parts: [{ type: "text", text: injectText }],
71
+ ...(session.agent ? { agent: session.agent } : {}),
77
72
  });
78
73
  await this.sql `
79
74
  UPDATE cron_jobs SET last_run = ${executedAt} WHERE id = ${job.id}
@@ -1,5 +1,4 @@
1
1
  import { Router } from "express";
2
- import { cwd } from "process";
3
2
  import { Cron as CronerInstance } from "croner";
4
3
  const MAIL_INJECTION_BLURB = [
5
4
  ``,
@@ -17,7 +16,10 @@ const MAIL_INJECTION_BLURB = [
17
16
  `Tip: For currency or prices, use code blocks. Example: put numbers in single or`,
18
17
  `double quotes to preserve formatting characters like dollar signs.`,
19
18
  ].join("\n");
20
- function generateWelcomeMessage(name, mode, status, humanName, humanDescription, token) {
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) {
21
23
  return [
22
24
  `╔══════════════════════════════════════════════════════╗`,
23
25
  `║ WELCOME TO THE AGENT OFFICE ║`,
@@ -25,7 +27,6 @@ function generateWelcomeMessage(name, mode, status, humanName, humanDescription,
25
27
  ``,
26
28
  `You are now clocked in.`,
27
29
  ` Name: ${name}`,
28
- ...(mode ? [` Mode: ${mode}`] : []),
29
30
  ...(status ? [` Status: ${status}`] : []),
30
31
  ` Human manager: ${humanName} — the human who created your`,
31
32
  ` session, assigns your work, and is your`,
@@ -135,8 +136,8 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
135
136
  });
136
137
  router.get("/modes", async (_req, res) => {
137
138
  try {
138
- const config = await opencode.config.get();
139
- const agent = config.agent ?? {};
139
+ const { data: config } = await opencode.config.get();
140
+ const agent = config?.agent ?? {};
140
141
  const modes = Object.entries(agent)
141
142
  .filter(([, val]) => val != null)
142
143
  .map(([name, val]) => ({
@@ -154,7 +155,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
154
155
  router.get("/sessions", async (_req, res) => {
155
156
  try {
156
157
  const rows = await sql `
157
- SELECT id, name, session_id, agent_code, mode, status, created_at
158
+ SELECT id, name, session_id, agent_code, agent, status, created_at
158
159
  FROM sessions
159
160
  ORDER BY created_at DESC
160
161
  `;
@@ -166,13 +167,13 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
166
167
  }
167
168
  });
168
169
  router.post("/sessions", async (req, res) => {
169
- const { name, mode } = req.body;
170
+ const { name, agent: agentArg } = req.body;
170
171
  if (!name || typeof name !== "string" || !name.trim()) {
171
172
  res.status(400).json({ error: "name is required" });
172
173
  return;
173
174
  }
174
175
  const trimmedName = name.trim();
175
- const trimmedMode = typeof mode === "string" && mode.trim() ? mode.trim() : null;
176
+ const trimmedAgent = typeof agentArg === "string" && agentArg.trim() ? agentArg.trim() : null;
176
177
  const existing = await sql `
177
178
  SELECT id FROM sessions WHERE name = ${trimmedName}
178
179
  `;
@@ -182,7 +183,9 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
182
183
  }
183
184
  let opencodeSessionId;
184
185
  try {
185
- const session = await opencode.session.create({ query: { directory: cwd() } });
186
+ const { data: session } = await opencode.session.create();
187
+ if (!session)
188
+ throw new Error("No session returned");
186
189
  opencodeSessionId = session.id;
187
190
  }
188
191
  catch (err) {
@@ -193,34 +196,29 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
193
196
  let row;
194
197
  try {
195
198
  const [inserted] = await sql `
196
- INSERT INTO sessions (name, session_id, mode)
197
- VALUES (${trimmedName}, ${opencodeSessionId}, ${trimmedMode})
198
- RETURNING id, name, session_id, agent_code, mode, created_at
199
+ INSERT INTO sessions (name, session_id, agent)
200
+ VALUES (${trimmedName}, ${opencodeSessionId}, ${trimmedAgent})
201
+ RETURNING id, name, session_id, agent_code, agent, created_at
199
202
  `;
200
203
  row = inserted;
201
204
  }
202
205
  catch (err) {
203
206
  console.error("DB insert error:", err);
204
207
  try {
205
- await opencode.session.delete(opencodeSessionId);
208
+ await opencode.session.delete({ sessionID: opencodeSessionId });
206
209
  }
207
210
  catch { }
208
211
  res.status(500).json({ error: "Internal server error" });
209
212
  return;
210
213
  }
211
214
  try {
212
- const providers = await opencode.app.providers();
213
- const defaultEntry = Object.entries(providers.default)[0];
214
- if (defaultEntry) {
215
- const clockInToken = `${row.agent_code}@${serverUrl}`;
216
- 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}`;
217
- await opencode.session.chat(opencodeSessionId, {
218
- modelID: defaultEntry[0],
219
- providerID: defaultEntry[1],
220
- parts: [{ type: "text", text: enrollmentMessage }],
221
- ...(trimmedMode ? { mode: trimmedMode } : {}),
222
- });
223
- }
215
+ const clockInToken = `${row.agent_code}@${serverUrl}`;
216
+ const enrollmentMessage = generateEnrollmentMessage(clockInToken);
217
+ await opencode.session.promptAsync({
218
+ sessionID: opencodeSessionId,
219
+ parts: [{ type: "text", text: enrollmentMessage }],
220
+ ...(trimmedAgent ? { agent: trimmedAgent } : {}),
221
+ });
224
222
  }
225
223
  catch (err) {
226
224
  console.warn("Warning: could not send first message to session:", err);
@@ -262,8 +260,8 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
262
260
  }
263
261
  const row = rows[0];
264
262
  try {
265
- const messages = await opencode.session.messages(row.session_id);
266
- const result = messages
263
+ const { data: messages } = await opencode.session.messages({ sessionID: row.session_id, limit });
264
+ const result = (messages ?? [])
267
265
  .slice(-limit)
268
266
  .map((m) => ({
269
267
  role: m.info.role,
@@ -311,42 +309,22 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
311
309
  return;
312
310
  }
313
311
  const row = rows[0];
314
- let resolvedModelID = modelID;
315
- let resolvedProviderID = providerID;
316
- if (!resolvedModelID || !resolvedProviderID) {
317
- try {
318
- const providers = await opencode.app.providers();
319
- const defaultEntry = Object.entries(providers.default)[0];
320
- if (!defaultEntry) {
321
- res.status(502).json({ error: "No default model configured in OpenCode" });
322
- return;
323
- }
324
- resolvedModelID = resolvedModelID ?? defaultEntry[0];
325
- resolvedProviderID = resolvedProviderID ?? defaultEntry[1];
326
- }
327
- catch (err) {
328
- console.error("OpenCode app.providers error:", err);
329
- res.status(502).json({ error: "Failed to fetch providers from OpenCode", detail: String(err) });
330
- return;
331
- }
332
- }
333
312
  try {
334
- const response = await opencode.session.chat(row.session_id, {
335
- modelID: resolvedModelID,
336
- providerID: resolvedProviderID,
313
+ await opencode.session.promptAsync({
314
+ sessionID: row.session_id,
337
315
  parts: [{ type: "text", text: text.trim() }],
338
316
  });
339
- res.json({ ok: true, messageID: response.id });
317
+ res.json({ ok: true });
340
318
  }
341
319
  catch (err) {
342
- console.error("OpenCode session.chat error:", err);
320
+ console.error("OpenCode session.prompt error:", err);
343
321
  res.status(502).json({ error: "Failed to inject message into OpenCode session", detail: String(err) });
344
322
  }
345
323
  });
346
324
  router.post("/sessions/:name/revert-to-start", async (req, res) => {
347
325
  const { name } = req.params;
348
326
  const rows = await sql `
349
- SELECT id, name, session_id, agent_code, mode, status, created_at FROM sessions WHERE name = ${name}
327
+ SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions WHERE name = ${name}
350
328
  `;
351
329
  if (rows.length === 0) {
352
330
  res.status(404).json({ error: `Session "${name}" not found` });
@@ -354,8 +332,8 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
354
332
  }
355
333
  const session = rows[0];
356
334
  try {
357
- const messages = await opencode.session.messages(session.session_id);
358
- if (messages.length === 0) {
335
+ const { data: messages } = await opencode.session.messages({ sessionID: session.session_id });
336
+ if (!messages || messages.length === 0) {
359
337
  res.status(400).json({ error: "Session has no messages to revert to" });
360
338
  return;
361
339
  }
@@ -366,20 +344,14 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
366
344
  }
367
345
  // Abort any in-progress generation before reverting, to avoid "session is busy" errors.
368
346
  // Swallow errors — the session may not be busy, in which case abort is a no-op.
369
- await opencode.session.abort(session.session_id).catch(() => { });
370
- await opencode.session.revert(session.session_id, { messageID: firstMessage.info.id });
371
- const providers = await opencode.app.providers();
372
- const defaultEntry = Object.entries(providers.default)[0];
373
- if (!defaultEntry) {
374
- res.status(502).json({ error: "No default model configured in OpenCode" });
375
- return;
376
- }
347
+ await opencode.session.abort({ sessionID: session.session_id }).catch(() => { });
348
+ await opencode.session.revert({ sessionID: session.session_id, messageID: firstMessage.info.id });
377
349
  const clockInToken = `${session.agent_code}@${serverUrl}`;
378
- 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}`;
379
- await opencode.session.chat(session.session_id, {
380
- modelID: defaultEntry[0],
381
- providerID: defaultEntry[1],
350
+ const enrollmentMessage = generateEnrollmentMessage(clockInToken);
351
+ await opencode.session.promptAsync({
352
+ sessionID: session.session_id,
382
353
  parts: [{ type: "text", text: enrollmentMessage }],
354
+ ...(session.agent ? { agent: session.agent } : {}),
383
355
  });
384
356
  res.json({ ok: true, messageID: firstMessage.info.id });
385
357
  }
@@ -390,19 +362,13 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
390
362
  });
391
363
  router.post("/sessions/revert-all", async (_req, res) => {
392
364
  const allSessions = await sql `
393
- SELECT id, name, session_id, agent_code, mode, status, created_at FROM sessions
365
+ SELECT id, name, session_id, agent_code, agent, status, created_at FROM sessions
394
366
  `;
395
- const providers = await opencode.app.providers();
396
- const defaultEntry = Object.entries(providers.default)[0];
397
- if (!defaultEntry) {
398
- res.status(502).json({ error: "No default model configured in OpenCode" });
399
- return;
400
- }
401
367
  const results = [];
402
368
  for (const session of allSessions) {
403
369
  try {
404
- const messages = await opencode.session.messages(session.session_id);
405
- if (messages.length === 0) {
370
+ const { data: messages } = await opencode.session.messages({ sessionID: session.session_id });
371
+ if (!messages || messages.length === 0) {
406
372
  results.push({ name: session.name, ok: false, error: "No messages" });
407
373
  continue;
408
374
  }
@@ -412,14 +378,14 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
412
378
  continue;
413
379
  }
414
380
  // Abort any in-progress generation before reverting
415
- await opencode.session.abort(session.session_id).catch(() => { });
416
- await opencode.session.revert(session.session_id, { messageID: firstMessage.info.id });
381
+ await opencode.session.abort({ sessionID: session.session_id }).catch(() => { });
382
+ await opencode.session.revert({ sessionID: session.session_id, messageID: firstMessage.info.id });
417
383
  const clockInToken = `${session.agent_code}@${serverUrl}`;
418
- 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}`;
419
- await opencode.session.chat(session.session_id, {
420
- modelID: defaultEntry[0],
421
- providerID: defaultEntry[1],
384
+ const enrollmentMessage = generateEnrollmentMessage(clockInToken);
385
+ await opencode.session.promptAsync({
386
+ sessionID: session.session_id,
422
387
  parts: [{ type: "text", text: enrollmentMessage }],
388
+ ...(session.agent ? { agent: session.agent } : {}),
423
389
  });
424
390
  results.push({ name: session.name, ok: true });
425
391
  }
@@ -442,7 +408,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
442
408
  }
443
409
  const row = rows[0];
444
410
  try {
445
- await opencode.session.delete(row.session_id);
411
+ await opencode.session.delete({ sessionID: row.session_id });
446
412
  }
447
413
  catch (err) {
448
414
  console.error("OpenCode session.delete error:", err);
@@ -565,8 +531,6 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
565
531
  res.status(400).json({ error: "No valid recipients found" });
566
532
  return;
567
533
  }
568
- const providers = await opencode.app.providers();
569
- const defaultEntry = Object.entries(providers.default)[0];
570
534
  const results = [];
571
535
  for (const recipient of validRecipients) {
572
536
  const [msgRow] = await sql `
@@ -576,13 +540,12 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
576
540
  `;
577
541
  const msgId = msgRow.id;
578
542
  let injected = false;
579
- if (sessionMap.has(recipient) && defaultEntry) {
543
+ if (sessionMap.has(recipient)) {
580
544
  const sessionId = sessionMap.get(recipient);
581
545
  const injectText = `[Message from "${trimmedFrom}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
582
546
  try {
583
- await opencode.session.chat(sessionId, {
584
- modelID: defaultEntry[0],
585
- providerID: defaultEntry[1],
547
+ await opencode.session.promptAsync({
548
+ sessionID: sessionId,
586
549
  parts: [{ type: "text", text: injectText }],
587
550
  });
588
551
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
@@ -597,7 +560,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
597
560
  res.status(201).json({ ok: true, results });
598
561
  });
599
562
  router.post("/messages/:id/read", async (req, res) => {
600
- const id = parseInt(req.params.id, 10);
563
+ const id = parseInt(String(req.params.id), 10);
601
564
  if (isNaN(id)) {
602
565
  res.status(400).json({ error: "Invalid message id" });
603
566
  return;
@@ -738,7 +701,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
738
701
  }
739
702
  });
740
703
  router.delete("/crons/:id", async (req, res) => {
741
- const id = parseInt(req.params.id, 10);
704
+ const id = parseInt(String(req.params.id), 10);
742
705
  if (isNaN(id)) {
743
706
  res.status(400).json({ error: "Invalid cron job id" });
744
707
  return;
@@ -759,7 +722,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
759
722
  }
760
723
  });
761
724
  router.post("/crons/:id/enable", async (req, res) => {
762
- const id = parseInt(req.params.id, 10);
725
+ const id = parseInt(String(req.params.id), 10);
763
726
  if (isNaN(id)) {
764
727
  res.status(400).json({ error: "Invalid cron job id" });
765
728
  return;
@@ -809,7 +772,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
809
772
  }
810
773
  });
811
774
  router.post("/crons/:id/disable", async (req, res) => {
812
- const id = parseInt(req.params.id, 10);
775
+ const id = parseInt(String(req.params.id), 10);
813
776
  if (isNaN(id)) {
814
777
  res.status(400).json({ error: "Invalid cron job id" });
815
778
  return;
@@ -841,7 +804,7 @@ export function createRouter(sql, opencode, serverUrl, scheduler, memoryManager)
841
804
  }
842
805
  });
843
806
  router.get("/crons/:id/history", async (req, res) => {
844
- const id = parseInt(req.params.id, 10);
807
+ const id = parseInt(String(req.params.id), 10);
845
808
  const limit = Math.min(parseInt(req.query.limit ?? "10", 10), 100);
846
809
  if (isNaN(id)) {
847
810
  res.status(400).json({ error: "Invalid cron job id" });
@@ -1012,109 +975,11 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1012
975
  }
1013
976
  const session = rows[0];
1014
977
  const token = `${session.agent_code}@<server-url>`;
1015
- const name = session.name;
1016
978
  const humanConfig = await sql `SELECT value FROM config WHERE key = 'human_name'`;
1017
979
  const humanName = humanConfig[0]?.value ?? "your human manager";
1018
980
  const humanDescConfig = await sql `SELECT value FROM config WHERE key = 'human_description'`;
1019
981
  const humanDescription = humanDescConfig[0]?.value ?? "";
1020
- const message = [
1021
- `╔══════════════════════════════════════════════════════╗`,
1022
- `║ WELCOME TO THE AGENT OFFICE ║`,
1023
- `╚══════════════════════════════════════════════════════╝`,
1024
- ``,
1025
- `You are now clocked in.`,
1026
- ` Name: ${name}`,
1027
- ...(session.mode ? [` Mode: ${session.mode}`] : []),
1028
- ` Human manager: ${humanName} — the human who created your`,
1029
- ` session, assigns your work, and is your`,
1030
- ` primary point of contact for questions,`,
1031
- ` updates, and decisions.`,
1032
- ...(humanDescription ? [
1033
- ` "${humanDescription}"`,
1034
- ] : []),
1035
- ``,
1036
- `The agent-office CLI is your PRIMARY means of communicating`,
1037
- `with your human manager (${humanName}) and your coworkers.`,
1038
- `Use it to send and receive messages, and to discover who`,
1039
- `else is working.`,
1040
- ``,
1041
- `════════════════════════════════════════════════════════`,
1042
- ` AVAILABLE COMMANDS`,
1043
- `════════════════════════════════════════════════════════`,
1044
- ``,
1045
- ` List your coworkers`,
1046
- ` agent-office worker list-coworkers \\`,
1047
- ` ${token}`,
1048
- ``,
1049
- ` Send a message to your manager or a coworker`,
1050
- ` agent-office worker send-message \\`,
1051
- ` --name <recipient-name> \\`,
1052
- ` --body "Your message here" \\`,
1053
- ` ${token}`,
1054
- ``,
1055
- ` Send a message to multiple recipients at once`,
1056
- ` agent-office worker send-message \\`,
1057
- ` --name alice --name bob \\`,
1058
- ` --body "Your message here" \\`,
1059
- ` ${token}`,
1060
- ``,
1061
- ` Manage scheduled tasks (optional)`,
1062
- ` agent-office worker cron \\`,
1063
- ` ${token}`,
1064
- ``,
1065
- ` Store a memory (persistent across sessions)`,
1066
- ` agent-office worker memory add \\`,
1067
- ` --content "your memory here" \\`,
1068
- ` ${token}`,
1069
- ``,
1070
- ` Search your memories`,
1071
- ` agent-office worker memory search \\`,
1072
- ` --query "your search" \\`,
1073
- ` ${token}`,
1074
- ``,
1075
- ` List all your memories`,
1076
- ` agent-office worker memory list \\`,
1077
- ` ${token}`,
1078
- ``,
1079
- ` Forget a memory`,
1080
- ` agent-office worker memory forget \\`,
1081
- ` ${token} <memory-id>`,
1082
- ``,
1083
- `════════════════════════════════════════════════════════`,
1084
- ` ⚠ IMPORTANT: YOUR SESSIONS ARE PRIVATE`,
1085
- `════════════════════════════════════════════════════════`,
1086
- ``,
1087
- ` Nobody — not ${humanName}, not your coworkers — can see`,
1088
- ` anything you think, reason, or write inside this session.`,
1089
- ` Your work is completely private until you explicitly send`,
1090
- ` a message using the send-message command.`,
1091
- ``,
1092
- ` This means:`,
1093
- ` • You must send a message to report progress or ask`,
1094
- ` for help. Silence looks like inaction.`,
1095
- ` • Finishing a task without messaging ${humanName}`,
1096
- ` means they will never know it is done.`,
1097
- ` • If you are blocked or uncertain, send a message —`,
1098
- ` nobody will know otherwise.`,
1099
- ``,
1100
- `════════════════════════════════════════════════════════`,
1101
- ` TIPS`,
1102
- `════════════════════════════════════════════════════════`,
1103
- ``,
1104
- ` - Run list-coworkers to discover who is available and`,
1105
- ` what their names are before sending messages.`,
1106
- ` - Messages you send are delivered directly into the`,
1107
- ` recipient's active session — they will see them`,
1108
- ` immediately.`,
1109
- ` - Your human manager is ${humanName}. They can send you`,
1110
- ` messages at any time and those will appear here in`,
1111
- ` your session just like this one. You can reach them`,
1112
- ` by sending a message to --name ${humanName}.`,
1113
- ` - Optional: Set up recurring scheduled tasks with cron`,
1114
- ` jobs. Run 'agent-office worker cron list ${token}' to`,
1115
- ` get started.`,
1116
- ``,
1117
- ].join("\n");
982
+ const message = generateWelcomeMessage(session.name, session.agent, session.status ?? null, humanName, humanDescription, token);
1118
983
  res.json({
1119
984
  ok: true,
1120
985
  name: session.name,
@@ -1164,7 +1029,7 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1164
1029
  return;
1165
1030
  }
1166
1031
  const rows = await sql `
1167
- SELECT id, name, session_id, agent_code, mode, status, created_at
1032
+ SELECT id, name, session_id, agent_code, agent, status, created_at
1168
1033
  FROM sessions
1169
1034
  WHERE agent_code = ${code}
1170
1035
  `;
@@ -1230,8 +1095,6 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1230
1095
  res.status(400).json({ error: "No valid recipients found" });
1231
1096
  return;
1232
1097
  }
1233
- const providers = await opencode.app.providers();
1234
- const defaultEntry = Object.entries(providers.default)[0];
1235
1098
  const results = [];
1236
1099
  for (const recipient of validRecipients) {
1237
1100
  const [msgRow] = await sql `
@@ -1241,13 +1104,12 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1241
1104
  `;
1242
1105
  const msgId = msgRow.id;
1243
1106
  let injected = false;
1244
- if (sessionMap.has(recipient) && defaultEntry) {
1107
+ if (sessionMap.has(recipient)) {
1245
1108
  const recipientSessionId = sessionMap.get(recipient);
1246
1109
  const injectText = `[Message from "${session.name}"]: ${trimmedBody}${MAIL_INJECTION_BLURB}`;
1247
1110
  try {
1248
- await opencode.session.chat(recipientSessionId, {
1249
- modelID: defaultEntry[0],
1250
- providerID: defaultEntry[1],
1111
+ await opencode.session.promptAsync({
1112
+ sessionID: recipientSessionId,
1251
1113
  parts: [{ type: "text", text: injectText }],
1252
1114
  });
1253
1115
  await sql `UPDATE messages SET injected = TRUE WHERE id = ${msgId}`;
@@ -1397,7 +1259,7 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1397
1259
  });
1398
1260
  router.delete("/worker/crons/:id", async (req, res) => {
1399
1261
  const { code } = req.query;
1400
- const id = parseInt(req.params.id, 10);
1262
+ const id = parseInt(String(req.params.id), 10);
1401
1263
  if (!code || typeof code !== "string") {
1402
1264
  res.status(400).json({ error: "code query parameter is required" });
1403
1265
  return;
@@ -1432,7 +1294,7 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1432
1294
  });
1433
1295
  router.post("/worker/crons/:id/enable", async (req, res) => {
1434
1296
  const { code } = req.query;
1435
- const id = parseInt(req.params.id, 10);
1297
+ const id = parseInt(String(req.params.id), 10);
1436
1298
  if (!code || typeof code !== "string") {
1437
1299
  res.status(400).json({ error: "code query parameter is required" });
1438
1300
  return;
@@ -1494,7 +1356,7 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1494
1356
  });
1495
1357
  router.post("/worker/crons/:id/disable", async (req, res) => {
1496
1358
  const { code } = req.query;
1497
- const id = parseInt(req.params.id, 10);
1359
+ const id = parseInt(String(req.params.id), 10);
1498
1360
  if (!code || typeof code !== "string") {
1499
1361
  res.status(400).json({ error: "code query parameter is required" });
1500
1362
  return;
@@ -1538,7 +1400,7 @@ export function createWorkerRouter(sql, opencode, serverUrl, memoryManager) {
1538
1400
  });
1539
1401
  router.get("/worker/crons/:id/history", async (req, res) => {
1540
1402
  const { code } = req.query;
1541
- const id = parseInt(req.params.id, 10);
1403
+ const id = parseInt(String(req.params.id), 10);
1542
1404
  const limit = Math.min(parseInt(req.query.limit ?? "10", 10), 100);
1543
1405
  if (!code || typeof code !== "string") {
1544
1406
  res.status(400).json({ error: "code query parameter is required" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-office",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Manage OpenCode sessions with named aliases",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,22 +33,21 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@inkjs/ui": "^2.0.0",
36
- "@opencode-ai/sdk": "^0.1.0-alpha.21",
37
- "agent-office": "^0.0.2",
38
- "commander": "^12.0.0",
36
+ "@opencode-ai/sdk": "^1.2.10",
37
+ "commander": "^14.0.0",
39
38
  "croner": "^10.0.1",
40
- "dotenv": "^16.0.0",
41
- "express": "^4.18.0",
39
+ "dotenv": "^17.0.0",
40
+ "express": "^5.0.0",
42
41
  "fastmemory": "^0.1.5",
43
- "ink": "^5.0.0",
42
+ "ink": "^6.0.0",
44
43
  "postgres": "^3.4.0",
45
- "react": "^18.0.0"
44
+ "react": "^19.0.0"
46
45
  },
47
46
  "devDependencies": {
48
47
  "@types/better-sqlite3": "^7.6.13",
49
- "@types/express": "^4.17.0",
50
- "@types/node": "^20.0.0",
51
- "@types/react": "^18.0.0",
48
+ "@types/express": "^5.0.0",
49
+ "@types/node": "^22.0.0",
50
+ "@types/react": "^19.0.0",
52
51
  "tsx": "^4.0.0",
53
52
  "typescript": "^5.0.0"
54
53
  }