@agentmeshhq/agent 0.1.11 → 0.1.13

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.
Files changed (74) hide show
  1. package/dist/__tests__/injector.test.d.ts +1 -0
  2. package/dist/__tests__/injector.test.js +26 -0
  3. package/dist/__tests__/injector.test.js.map +1 -0
  4. package/dist/__tests__/sandbox.test.d.ts +1 -0
  5. package/dist/__tests__/sandbox.test.js +362 -0
  6. package/dist/__tests__/sandbox.test.js.map +1 -0
  7. package/dist/cli/build.d.ts +6 -0
  8. package/dist/cli/build.js +111 -0
  9. package/dist/cli/build.js.map +1 -0
  10. package/dist/cli/deploy.d.ts +9 -0
  11. package/dist/cli/deploy.js +130 -0
  12. package/dist/cli/deploy.js.map +1 -0
  13. package/dist/cli/inbox.d.ts +5 -0
  14. package/dist/cli/inbox.js +123 -0
  15. package/dist/cli/inbox.js.map +1 -0
  16. package/dist/cli/index.js +159 -0
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cli/issue.d.ts +42 -0
  19. package/dist/cli/issue.js +297 -0
  20. package/dist/cli/issue.js.map +1 -0
  21. package/dist/cli/local.d.ts +9 -0
  22. package/dist/cli/local.js +139 -0
  23. package/dist/cli/local.js.map +1 -0
  24. package/dist/cli/migrate.d.ts +8 -0
  25. package/dist/cli/migrate.js +167 -0
  26. package/dist/cli/migrate.js.map +1 -0
  27. package/dist/cli/ready.d.ts +5 -0
  28. package/dist/cli/ready.js +131 -0
  29. package/dist/cli/ready.js.map +1 -0
  30. package/dist/cli/slack.d.ts +3 -0
  31. package/dist/cli/slack.js +57 -0
  32. package/dist/cli/slack.js.map +1 -0
  33. package/dist/cli/start.d.ts +12 -0
  34. package/dist/cli/start.js +14 -0
  35. package/dist/cli/start.js.map +1 -1
  36. package/dist/cli/sync.d.ts +8 -0
  37. package/dist/cli/sync.js +154 -0
  38. package/dist/cli/sync.js.map +1 -0
  39. package/dist/cli/test.d.ts +8 -0
  40. package/dist/cli/test.js +110 -0
  41. package/dist/cli/test.js.map +1 -0
  42. package/dist/core/daemon.d.ts +31 -0
  43. package/dist/core/daemon.js +188 -28
  44. package/dist/core/daemon.js.map +1 -1
  45. package/dist/core/injector.d.ts +6 -1
  46. package/dist/core/injector.js +64 -1
  47. package/dist/core/injector.js.map +1 -1
  48. package/dist/core/issue-cache.d.ts +44 -0
  49. package/dist/core/issue-cache.js +75 -0
  50. package/dist/core/issue-cache.js.map +1 -0
  51. package/dist/core/registry.d.ts +5 -0
  52. package/dist/core/registry.js +8 -1
  53. package/dist/core/registry.js.map +1 -1
  54. package/dist/core/sandbox.d.ts +127 -0
  55. package/dist/core/sandbox.js +377 -0
  56. package/dist/core/sandbox.js.map +1 -0
  57. package/dist/core/tmux.js +8 -10
  58. package/dist/core/tmux.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/__tests__/injector.test.ts +29 -0
  61. package/src/__tests__/sandbox.test.ts +435 -0
  62. package/src/cli/build.ts +137 -0
  63. package/src/cli/deploy.ts +153 -0
  64. package/src/cli/index.ts +163 -0
  65. package/src/cli/local.ts +174 -0
  66. package/src/cli/migrate.ts +210 -0
  67. package/src/cli/slack.ts +69 -0
  68. package/src/cli/start.ts +22 -0
  69. package/src/cli/test.ts +141 -0
  70. package/src/core/daemon.ts +228 -37
  71. package/src/core/injector.ts +98 -1
  72. package/src/core/registry.ts +14 -1
  73. package/src/core/sandbox.ts +505 -0
  74. package/src/core/tmux.ts +9 -11
@@ -1,4 +1,4 @@
1
- import { execSync } from "node:child_process";
1
+ import { type ChildProcess, execSync, spawn } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
@@ -13,13 +13,14 @@ import type { AgentConfig, Config } from "../config/schema.js";
13
13
  import { loadContext, loadOrCreateContext, saveContext } from "../context/index.js";
14
14
  import { Heartbeat } from "./heartbeat.js";
15
15
  import { handleWebSocketEvent, injectRestoredContext, injectStartupMessage } from "./injector.js";
16
- import { checkInbox, fetchAssignments, registerAgent } from "./registry.js";
16
+ import { checkInbox, fetchAssignments, registerAgent, type ServerContext } from "./registry.js";
17
17
  import {
18
18
  buildRunnerConfig,
19
19
  detectRunner,
20
20
  getRunnerDisplayName,
21
21
  type RunnerConfig,
22
22
  } from "./runner.js";
23
+ import { DockerSandbox } from "./sandbox.js";
23
24
  import {
24
25
  captureSessionContext,
25
26
  createSession,
@@ -40,6 +41,18 @@ export interface DaemonOptions {
40
41
  restoreContext?: boolean;
41
42
  /** Auto-clone repository for project assignments */
42
43
  autoSetup?: boolean;
44
+ /** Run opencode serve instead of tmux TUI (for Integration Service) */
45
+ serve?: boolean;
46
+ /** Port for opencode serve (default: 3001) */
47
+ servePort?: number;
48
+ /** Run agent in Docker sandbox container */
49
+ sandbox?: boolean;
50
+ /** Docker image for sandbox (default: agentmesh/agent-sandbox:latest) */
51
+ sandboxImage?: string;
52
+ /** CPU limit for sandbox (default: 1) */
53
+ sandboxCpu?: string;
54
+ /** Memory limit for sandbox (default: 2g) */
55
+ sandboxMemory?: string;
43
56
  }
44
57
 
45
58
  export class AgentDaemon {
@@ -55,6 +68,15 @@ export class AgentDaemon {
55
68
  private assignedProject: string | undefined;
56
69
  private shouldRestoreContext: boolean;
57
70
  private autoSetup: boolean;
71
+ private serveMode: boolean;
72
+ private servePort: number;
73
+ private serveProcess: ChildProcess | null = null;
74
+ private serverContext: ServerContext | undefined;
75
+ private sandboxMode: boolean;
76
+ private sandboxImage: string;
77
+ private sandboxCpu: string;
78
+ private sandboxMemory: string;
79
+ private sandbox: DockerSandbox | null = null;
58
80
 
59
81
  constructor(options: DaemonOptions) {
60
82
  const config = loadConfig();
@@ -85,6 +107,12 @@ export class AgentDaemon {
85
107
  if (options.model) agentConfig.model = options.model;
86
108
 
87
109
  this.agentConfig = agentConfig;
110
+ this.serveMode = options.serve === true;
111
+ this.servePort = options.servePort || 3001;
112
+ this.sandboxMode = options.sandbox === true;
113
+ this.sandboxImage = options.sandboxImage || "agentmesh/agent-sandbox:latest";
114
+ this.sandboxCpu = options.sandboxCpu || "1";
115
+ this.sandboxMemory = options.sandboxMemory || "2g";
88
116
 
89
117
  // Build runner configuration with model resolution
90
118
  this.runnerConfig = buildRunnerConfig({
@@ -110,6 +138,7 @@ export class AgentDaemon {
110
138
  // Register with hub first (needed for assignment check)
111
139
  console.log("Registering with AgentMesh hub...");
112
140
  const existingState = getAgentState(this.agentName);
141
+ console.log(`Existing state: ${existingState ? `agentId=${existingState.agentId}` : "none"}`);
113
142
 
114
143
  const registration = await registerAgent({
115
144
  url: this.config.hubUrl,
@@ -118,49 +147,66 @@ export class AgentDaemon {
118
147
  agentId: existingState?.agentId || this.agentConfig.agentId,
119
148
  agentName: this.agentName,
120
149
  model: this.agentConfig.model || this.config.defaults.model,
150
+ restoreContext: this.shouldRestoreContext,
121
151
  });
122
152
 
123
153
  this.agentId = registration.agentId;
124
154
  this.token = registration.token;
125
155
 
126
- console.log(`Registered as: ${this.agentId}`);
156
+ if (registration.status === "re-registered") {
157
+ console.log(`Re-registered as: ${this.agentId}`);
158
+ if (registration.context && Object.keys(registration.context).length > 0) {
159
+ this.serverContext = registration.context;
160
+ console.log(`Server context restored: ${Object.keys(registration.context).join(", ")}`);
161
+ }
162
+ } else {
163
+ console.log(`Registered as: ${this.agentId}`);
164
+ }
127
165
 
128
166
  // Check assignments and auto-setup workdir if needed (before creating tmux session)
129
167
  await this.checkAssignments();
130
168
 
131
- // Check if session already exists
132
- const sessionName = getSessionName(this.agentName);
133
- const sessionAlreadyExists = sessionExists(sessionName);
134
-
135
- // Create tmux session if it doesn't exist
136
- if (!sessionAlreadyExists) {
137
- console.log(`Creating tmux session: ${sessionName}`);
138
-
139
- // Include runner env vars (e.g., OPENCODE_MODEL) at session creation
140
- const created = createSession(
141
- this.agentName,
142
- this.agentConfig.command,
143
- this.agentConfig.workdir,
144
- this.runnerConfig.env, // Apply model env at process start
145
- );
146
-
147
- if (!created) {
148
- throw new Error("Failed to create tmux session");
149
- }
169
+ // Choose runtime mode: sandbox > serve > tmux
170
+ if (this.sandboxMode) {
171
+ await this.startSandboxMode();
172
+ } else if (this.serveMode) {
173
+ await this.startServeMode();
150
174
  } else {
151
- console.log(`Reconnecting to existing session: ${sessionName}`);
152
- // Update environment for existing session
153
- updateSessionEnvironment(this.agentName, this.runnerConfig.env);
154
- }
175
+ // Check if session already exists
176
+ const sessionName = getSessionName(this.agentName);
177
+ const sessionAlreadyExists = sessionExists(sessionName);
178
+
179
+ // Create tmux session if it doesn't exist
180
+ if (!sessionAlreadyExists) {
181
+ console.log(`Creating tmux session: ${sessionName}`);
182
+
183
+ // Include runner env vars (e.g., OPENCODE_MODEL) at session creation
184
+ const created = createSession(
185
+ this.agentName,
186
+ this.agentConfig.command,
187
+ this.agentConfig.workdir,
188
+ this.runnerConfig.env, // Apply model env at process start
189
+ );
190
+
191
+ if (!created) {
192
+ throw new Error("Failed to create tmux session");
193
+ }
194
+ } else {
195
+ console.log(`Reconnecting to existing session: ${sessionName}`);
196
+ // Update environment for existing session
197
+ updateSessionEnvironment(this.agentName, this.runnerConfig.env);
198
+ }
155
199
 
156
- // Inject environment variables into tmux session
157
- console.log("Injecting environment variables...");
158
- updateSessionEnvironment(this.agentName, {
159
- AGENT_TOKEN: this.token,
160
- AGENTMESH_AGENT_ID: this.agentId,
161
- });
200
+ // Inject environment variables into tmux session
201
+ console.log("Injecting environment variables...");
202
+ updateSessionEnvironment(this.agentName, {
203
+ AGENT_TOKEN: this.token,
204
+ AGENTMESH_AGENT_ID: this.agentId,
205
+ });
206
+ }
162
207
 
163
208
  // Save state including runtime model info
209
+ const sessionName = this.serveMode ? `serve:${this.servePort}` : getSessionName(this.agentName);
164
210
  addAgentToState({
165
211
  name: this.agentName,
166
212
  agentId: this.agentId,
@@ -211,6 +257,7 @@ export class AgentDaemon {
211
257
  url: `${wsUrl}/ws/v1`,
212
258
  token: newToken,
213
259
  onMessage: (event) => {
260
+ console.log(`[WS] Received event: ${event.type}`);
214
261
  handleWebSocketEvent(this.agentName, event);
215
262
  },
216
263
  onConnect: () => {
@@ -237,7 +284,11 @@ export class AgentDaemon {
237
284
  url: `${wsUrl}/ws/v1`,
238
285
  token: this.token,
239
286
  onMessage: (event) => {
240
- handleWebSocketEvent(this.agentName, event);
287
+ console.log(`[WS] Received event: ${event.type}`);
288
+ handleWebSocketEvent(this.agentName, event, {
289
+ hubUrl: this.config.hubUrl,
290
+ token: this.token ?? undefined,
291
+ });
241
292
  },
242
293
  onConnect: () => {
243
294
  console.log("WebSocket connected");
@@ -316,16 +367,156 @@ Nudge agent:
316
367
  this.ws = null;
317
368
  }
318
369
 
319
- // Destroy tmux session
320
- destroySession(this.agentName);
370
+ // Stop sandbox, serve process, or destroy tmux session
371
+ if (this.sandboxMode && this.sandbox) {
372
+ console.log("Stopping sandbox container...");
373
+ await this.sandbox.destroy();
374
+ this.sandbox = null;
375
+ } else if (this.serveMode && this.serveProcess) {
376
+ console.log("Stopping opencode serve...");
377
+ this.serveProcess.kill("SIGTERM");
378
+ this.serveProcess = null;
379
+ } else {
380
+ destroySession(this.agentName);
381
+ }
321
382
 
322
- // Remove from state
323
- removeAgentFromState(this.agentName);
383
+ // Update state to mark as stopped but preserve agentId for next restart
384
+ updateAgentInState(this.agentName, {
385
+ pid: 0,
386
+ tmuxSession: "",
387
+ startedAt: "",
388
+ token: undefined,
389
+ });
324
390
 
325
391
  console.log("Agent stopped.");
326
392
  process.exit(0);
327
393
  }
328
394
 
395
+ /**
396
+ * Starts opencode serve mode (for Integration Service)
397
+ * Replaces tmux with a direct HTTP server
398
+ */
399
+ private async startServeMode(): Promise<void> {
400
+ console.log(`Starting opencode serve mode on port ${this.servePort}...`);
401
+
402
+ const workdir = this.agentConfig.workdir || process.cwd();
403
+
404
+ // Build environment for opencode serve
405
+ const env: Record<string, string> = {
406
+ ...process.env,
407
+ ...this.runnerConfig.env,
408
+ AGENT_TOKEN: this.token!,
409
+ AGENTMESH_AGENT_ID: this.agentId!,
410
+ } as Record<string, string>;
411
+
412
+ // Spawn opencode serve as a child process
413
+ this.serveProcess = spawn(
414
+ "opencode",
415
+ ["serve", "--port", String(this.servePort), "--hostname", "0.0.0.0"],
416
+ {
417
+ cwd: workdir,
418
+ env,
419
+ stdio: ["ignore", "inherit", "inherit"],
420
+ },
421
+ );
422
+
423
+ // Handle process exit
424
+ this.serveProcess.on("exit", (code, signal) => {
425
+ console.error(`opencode serve exited with code ${code}, signal ${signal}`);
426
+ if (this.isRunning) {
427
+ console.log("Restarting opencode serve in 5 seconds...");
428
+ setTimeout(() => {
429
+ if (this.isRunning) {
430
+ this.startServeMode().catch(console.error);
431
+ }
432
+ }, 5000);
433
+ }
434
+ });
435
+
436
+ this.serveProcess.on("error", (error) => {
437
+ console.error("Failed to start opencode serve:", error);
438
+ });
439
+
440
+ // Wait a moment for the server to start
441
+ await new Promise((resolve) => setTimeout(resolve, 2000));
442
+
443
+ console.log(`opencode serve started on http://0.0.0.0:${this.servePort}`);
444
+ }
445
+
446
+ /**
447
+ * Starts agent in Docker sandbox mode
448
+ * Provides filesystem isolation with only workspace mounted
449
+ */
450
+ private async startSandboxMode(): Promise<void> {
451
+ console.log("Starting in Docker sandbox mode...");
452
+
453
+ // Check Docker availability
454
+ if (!DockerSandbox.checkDockerAvailable()) {
455
+ throw new Error(
456
+ "Docker is not available. Install Docker or use --sandbox host to run on host.",
457
+ );
458
+ }
459
+
460
+ const workdir = this.agentConfig.workdir || process.cwd();
461
+
462
+ // Check for existing sandbox container
463
+ const existingContainer = DockerSandbox.findExisting(this.agentName);
464
+ if (existingContainer) {
465
+ console.log(`Found existing sandbox container: ${existingContainer}`);
466
+ console.log("Stop it with: agentmesh stop " + this.agentName);
467
+ throw new Error("Sandbox container already exists");
468
+ }
469
+
470
+ // Create sandbox configuration
471
+ this.sandbox = new DockerSandbox({
472
+ agentName: this.agentName,
473
+ image: this.sandboxImage,
474
+ workspacePath: workdir,
475
+ cpuLimit: this.sandboxCpu,
476
+ memoryLimit: this.sandboxMemory,
477
+ env: {
478
+ ...this.runnerConfig.env,
479
+ AGENT_TOKEN: this.token!,
480
+ AGENTMESH_AGENT_ID: this.agentId!,
481
+ },
482
+ serveMode: this.serveMode,
483
+ servePort: this.servePort,
484
+ networkMode: "bridge",
485
+ });
486
+
487
+ // Validate mount policy (will throw if denied)
488
+ this.sandbox.validateMountPolicy();
489
+
490
+ // Pull image if needed
491
+ await this.sandbox.pullImage();
492
+
493
+ // Start container
494
+ await this.sandbox.start();
495
+
496
+ console.log(`
497
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
498
+ 🐳 SANDBOX MODE ACTIVE
499
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
500
+
501
+ Container: ${this.sandbox.getContainerName()}
502
+ Image: ${this.sandboxImage}
503
+ Workspace: ${workdir} -> /workspace
504
+ CPU: ${this.sandboxCpu} core(s)
505
+ Memory: ${this.sandboxMemory}
506
+
507
+ The agent is running in an isolated Docker container.
508
+ Only the workspace directory is accessible.
509
+
510
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
511
+ `);
512
+
513
+ // Start opencode in the container
514
+ if (!this.serveMode) {
515
+ console.log("Starting opencode in sandbox container...");
516
+ await this.sandbox.spawnOpencode();
517
+ }
518
+ }
519
+
329
520
  /**
330
521
  * Saves the current agent context to disk
331
522
  */
@@ -1,3 +1,5 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import { formatHandoffContextSummary, parseHandoffContext } from "../context/handoff.js";
2
4
  import type { AgentContext } from "../context/schema.js";
3
5
  import type { InboxItem } from "./registry.js";
@@ -65,6 +67,85 @@ You can now proceed with your work.`;
65
67
  sendKeys(agentName, message);
66
68
  }
67
69
 
70
+ export interface EventContext {
71
+ hubUrl?: string;
72
+ token?: string;
73
+ }
74
+
75
+ interface SlackFile {
76
+ name: string;
77
+ type: string;
78
+ url: string;
79
+ permalink: string;
80
+ base64?: string;
81
+ mediaType?: string;
82
+ }
83
+
84
+ export function injectSlackMessage(
85
+ agentName: string,
86
+ event: WebSocketEvent,
87
+ context?: EventContext,
88
+ ): void {
89
+ const user = (event.user as string) || "unknown";
90
+ const channel = (event.channel as string) || "unknown";
91
+ const text = (event.text as string) || "";
92
+ const files = (event.files as SlackFile[] | undefined) || [];
93
+
94
+ // Build message with file info
95
+ let message = `[Slack from ${user} in ${channel}] ${text}`;
96
+
97
+ if (files.length > 0) {
98
+ const savedImages: string[] = [];
99
+ const otherFiles: string[] = [];
100
+
101
+ // Save images with base64 data to disk
102
+ const attachmentsDir = join(process.env.HOME || "/tmp", ".agentmesh", "attachments");
103
+ if (!existsSync(attachmentsDir)) {
104
+ mkdirSync(attachmentsDir, { recursive: true });
105
+ }
106
+
107
+ for (const f of files) {
108
+ console.log(
109
+ `[Injector] File: ${f.name}, type: ${f.type}, mediaType: ${f.mediaType}, has base64: ${!!f.base64}`,
110
+ );
111
+ if (f.base64 && (f.mediaType?.startsWith("image/") || f.type?.startsWith("image/"))) {
112
+ // Save image to disk
113
+ const timestamp = Date.now();
114
+ const safeName = f.name.replace(/[^a-zA-Z0-9._-]/g, "_");
115
+ const filePath = join(attachmentsDir, `${timestamp}-${safeName}`);
116
+
117
+ try {
118
+ writeFileSync(filePath, Buffer.from(f.base64, "base64"));
119
+ savedImages.push(filePath);
120
+ console.log(`[Injector] Saved image to: ${filePath}`);
121
+ } catch (err) {
122
+ console.error(`[Injector] Failed to save image:`, err);
123
+ otherFiles.push(`${f.name} (failed to save)`);
124
+ }
125
+ } else {
126
+ // Just reference the URL for non-image files
127
+ otherFiles.push(`${f.name}: ${f.permalink}`);
128
+ }
129
+ }
130
+
131
+ // Add instruction to view images - OpenCode needs explicit instruction
132
+ if (savedImages.length > 0) {
133
+ message += `\n\nUser attached ${savedImages.length} image(s). Use the Read tool to view them:`;
134
+ for (const img of savedImages) {
135
+ message += `\n- ${img}`;
136
+ }
137
+ }
138
+
139
+ if (otherFiles.length > 0) {
140
+ message += ` [Other files: ${otherFiles.join(" | ")}]`;
141
+ }
142
+ }
143
+
144
+ console.log(`[Injector] Sending to tmux: ${message}`);
145
+ const result = sendKeys(agentName, message);
146
+ console.log(`[Injector] sendKeys result: ${result}`);
147
+ }
148
+
68
149
  export function injectInboxItems(agentName: string, items: InboxItem[]): void {
69
150
  if (items.length === 0) {
70
151
  sendKeys(agentName, "[AgentMesh] Your inbox is empty.");
@@ -85,7 +166,11 @@ export function injectInboxItems(agentName: string, items: InboxItem[]): void {
85
166
  sendKeys(agentName, message);
86
167
  }
87
168
 
88
- export function handleWebSocketEvent(agentName: string, event: WebSocketEvent): void {
169
+ export function handleWebSocketEvent(
170
+ agentName: string,
171
+ event: WebSocketEvent,
172
+ context?: EventContext,
173
+ ): void {
89
174
  switch (event.type) {
90
175
  case "handoff_received":
91
176
  case "handoff.received":
@@ -102,6 +187,18 @@ export function handleWebSocketEvent(agentName: string, event: WebSocketEvent):
102
187
  injectBlockerResolved(agentName, event);
103
188
  break;
104
189
 
190
+ case "slack.message":
191
+ console.log(`[Injector] Handling slack.message for ${agentName}`);
192
+ console.log(`[Injector] Event keys: ${Object.keys(event).join(", ")}`);
193
+ if (event.files) {
194
+ const files = event.files as Array<{ name: string; base64?: string }>;
195
+ console.log(
196
+ `[Injector] Files count: ${files.length}, has base64: ${files.some((f) => !!f.base64)}`,
197
+ );
198
+ }
199
+ injectSlackMessage(agentName, event, context);
200
+ break;
201
+
105
202
  default:
106
203
  // Unknown event type, ignore
107
204
  break;
@@ -8,11 +8,17 @@ export interface RegisterOptions {
8
8
  agentName: string;
9
9
  model: string;
10
10
  capabilities?: string[];
11
+ restoreContext?: boolean;
11
12
  }
12
13
 
14
+ export type ServerContext = Record<string, Record<string, unknown>>;
15
+
13
16
  export interface RegisterResult {
14
17
  agentId: string;
15
18
  token: string;
19
+ status?: "registered" | "re-registered";
20
+ context?: ServerContext;
21
+ assignments?: ProjectAssignment[];
16
22
  }
17
23
 
18
24
  export interface InboxItem {
@@ -42,6 +48,7 @@ export async function registerAgent(options: RegisterOptions): Promise<RegisterR
42
48
  model: options.model,
43
49
  capabilities: options.capabilities || ["coding", "review", "debugging"],
44
50
  workspace: options.workspace,
51
+ restore_context: options.restoreContext ?? true,
45
52
  }),
46
53
  });
47
54
 
@@ -57,7 +64,13 @@ export async function registerAgent(options: RegisterOptions): Promise<RegisterR
57
64
  throw new Error("No token in registration response");
58
65
  }
59
66
 
60
- return { agentId, token };
67
+ return {
68
+ agentId,
69
+ token,
70
+ status: data.status,
71
+ context: data.context,
72
+ assignments: data.assignments,
73
+ };
61
74
  }
62
75
 
63
76
  export async function checkInbox(