@controlflow-ai/daemon 0.1.2 → 0.1.3

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 (61) hide show
  1. package/README.md +54 -6
  2. package/package.json +3 -1
  3. package/src/agent-avatar.ts +30 -0
  4. package/src/agent-key.ts +28 -0
  5. package/src/agent-permissions.ts +359 -0
  6. package/src/agent-runtime.ts +795 -28
  7. package/src/agent-workspace.ts +183 -0
  8. package/src/app.ts +1970 -79
  9. package/src/args.ts +54 -7
  10. package/src/cli.ts +873 -14
  11. package/src/client.ts +472 -10
  12. package/src/coco.ts +9 -40
  13. package/src/codex.ts +33 -5
  14. package/src/config.ts +28 -4
  15. package/src/console.ts +230 -20
  16. package/src/daemon-client.ts +116 -3
  17. package/src/daemon.ts +936 -98
  18. package/src/db.ts +3128 -122
  19. package/src/delivery-ws.ts +269 -0
  20. package/src/format.ts +4 -1
  21. package/src/lark/cli.ts +3 -3
  22. package/src/lark/event-router.ts +60 -4
  23. package/src/lark/inbound-events.ts +156 -3
  24. package/src/lark/server-integration.ts +659 -111
  25. package/src/lark/ws-daemon.ts +136 -10
  26. package/src/local-api.ts +545 -15
  27. package/src/local-auth.ts +33 -1
  28. package/src/message-attachments.ts +71 -0
  29. package/src/messaging-cli.ts +741 -0
  30. package/src/messaging-status.ts +669 -0
  31. package/src/migrations/024_agents_model.ts +10 -0
  32. package/src/migrations/025_room_archive.ts +44 -0
  33. package/src/migrations/026_project_archive.ts +44 -0
  34. package/src/migrations/027_agent_permission_profiles.ts +16 -0
  35. package/src/migrations/028_lark_websocket_restart_state.ts +16 -0
  36. package/src/migrations/029_held_message_drafts.ts +32 -0
  37. package/src/migrations/030_agent_room_read_state.ts +25 -0
  38. package/src/migrations/031_room_tasks.ts +29 -0
  39. package/src/migrations/032_room_reminders.ts +29 -0
  40. package/src/migrations/033_room_saved_messages.ts +25 -0
  41. package/src/migrations/034_agent_activity_events.ts +27 -0
  42. package/src/migrations/035_agent_avatars.ts +17 -0
  43. package/src/migrations/036_project_agent_defaults.ts +21 -0
  44. package/src/migrations/037_message_attachments.ts +36 -0
  45. package/src/migrations/038_agent_activity_room_scope.ts +64 -0
  46. package/src/migrations/039_message_attachments_path.ts +34 -0
  47. package/src/migrations/040_message_attachments_file_schema.ts +80 -0
  48. package/src/migrations/041_room_system_events.ts +30 -0
  49. package/src/migrations/042_message_attachment_file_kind.ts +52 -0
  50. package/src/migrations/043_room_mode_skill_registry.ts +92 -0
  51. package/src/migrations/044_workflow_runtime.ts +69 -0
  52. package/src/migrations/045_skill_repository_ownership.ts +64 -0
  53. package/src/migrations.ts +69 -1
  54. package/src/neeko.ts +40 -4
  55. package/src/runtime-env.ts +179 -0
  56. package/src/runtime-registry.ts +83 -13
  57. package/src/server.ts +244 -4
  58. package/src/token-file.ts +13 -6
  59. package/src/types.ts +362 -0
  60. package/src/workflow-runtime.ts +275 -0
  61. package/src/web.ts +0 -904
package/src/client.ts CHANGED
@@ -1,6 +1,73 @@
1
1
  import { defaultServerToken, defaultServerUrl } from './config.js';
2
+ import { HttpError } from './http.js';
2
3
  import { serverAuthHeaders } from './server-auth.js';
3
- import type { AgentRoomSubscriptionMode, AgentRun, AgentSession, ApiResponse, Chat, Computer, ComputerAgentAssignment, ComputerConnection, DaemonInstance, Message, MessageDelivery, ProvisionedComputer, RemoteFileList, RoomChannel, RoomParticipant, RunAction } from './types.js';
4
+ import type { AgentActivityEvent, AgentActivityKind, AgentDeletionResult, AgentPermissionProfile, AgentRoomSubscription, AgentRoomSubscriptionMode, AgentRun, AgentSession, AgentWorkbench, ApiResponse, Chat, Computer, ComputerAgentAssignment, ComputerConnection, ComputerDeletionImpact, ComputerDeletionResult, DaemonInstance, DeliveryContext, EnabledSkill, HeldDraftStatus, HeldMessageDraft, Message, MessageDelivery, MessageFreshnessInput, ProvisionedComputer, RemoteFileList, RoomChannel, RoomParticipant, RoomReminder, RoomReminderStatus, RoomSavedMessage, RoomTask, RoomTaskStatus, RunAction } from './types.js';
5
+ export type { DeliveryBacklogSummary, DeliveryConnectionStats as DeliveryConnectionStatus, DeliveryWebSocketSummary, LarkMessagingStatus, MessagingDiagnostic, MessagingHealth, MessagingHealthDomain, MessagingStatus, MessagingStatusSummary } from './messaging-status.js';
6
+ import type { DeliveryBacklogSummary, DeliveryConnectionStats as DeliveryConnectionStatus, MessagingHealth, MessagingStatus } from './messaging-status.js';
7
+
8
+ export interface LarkRestartResult {
9
+ bots: string[];
10
+ restarted: string[];
11
+ missing: string[];
12
+ skipped_recent?: string[];
13
+ skipped_ineffective?: string[];
14
+ }
15
+
16
+ export interface LarkProbeEventResult {
17
+ app_id: string;
18
+ envelope: string;
19
+ raw_event_id: string;
20
+ inserted: boolean;
21
+ duplicate: boolean;
22
+ parse_ok: 0 | 1;
23
+ handled: boolean;
24
+ }
25
+
26
+ export interface LarkRecentEventSummary {
27
+ id: string;
28
+ received_at: string;
29
+ app_id: string;
30
+ event_type: string;
31
+ event_id: string;
32
+ parse_ok: 0 | 1;
33
+ bytes: number;
34
+ is_probe: boolean;
35
+ }
36
+
37
+ export interface LarkRepairParseResult {
38
+ dry_run: boolean;
39
+ scanned: number;
40
+ repaired: number;
41
+ unchanged: number;
42
+ conflicts: number;
43
+ errors: number;
44
+ rows: Array<{
45
+ id: string;
46
+ app_id: string;
47
+ old_event_id: string;
48
+ old_event_type: string;
49
+ new_event_id: string;
50
+ new_event_type: string;
51
+ status: 'repaired' | 'would_repair' | 'unchanged' | 'conflict' | 'error';
52
+ error?: string;
53
+ }>;
54
+ }
55
+
56
+ export interface DeliveryProbeResult {
57
+ message: Message;
58
+ deliveries: MessageDelivery[];
59
+ notify: {
60
+ deliveries: number;
61
+ target_connections: number;
62
+ open_sockets: number;
63
+ websocket_frames: number;
64
+ };
65
+ probe: {
66
+ token: string;
67
+ agent: string;
68
+ room: string;
69
+ };
70
+ }
4
71
 
5
72
  export interface SendRequest {
6
73
  chat?: string;
@@ -15,6 +82,102 @@ export interface SendRequest {
15
82
  type?: 'message' | 'system';
16
83
  idempotency_key?: string | null;
17
84
  mentions?: string[];
85
+ mention_lint?: 'strict' | 'literal' | 'infer';
86
+ attachments?: SendMessageAttachmentRequest[];
87
+ messages?: SendBatchMessageRequest[];
88
+ freshness?: MessageFreshnessInput;
89
+ }
90
+
91
+ export interface SendBatchMessageRequest {
92
+ parent_id?: number;
93
+ channel_id?: string | null;
94
+ recipient?: string | null;
95
+ content: string;
96
+ type?: 'message' | 'system';
97
+ idempotency_key?: string | null;
98
+ mentions?: string[];
99
+ attachments?: SendMessageAttachmentRequest[];
100
+ }
101
+
102
+ export interface SendMessageAttachmentRequest {
103
+ kind: 'image' | 'file';
104
+ mime_type: string;
105
+ filename?: string | null;
106
+ content_base64: string;
107
+ }
108
+
109
+ export interface SendBatchRequest extends Omit<SendRequest, 'content' | 'recipient' | 'mentions' | 'idempotency_key' | 'type' | 'parent_id' | 'channel_id'> {
110
+ messages: SendBatchMessageRequest[];
111
+ }
112
+
113
+ export interface SendMessageResult {
114
+ message?: Message;
115
+ messages?: Message[];
116
+ deliveries?: MessageDelivery[];
117
+ notify?: unknown;
118
+ draft?: HeldMessageDraft;
119
+ drafts?: HeldMessageDraft[];
120
+ intervening_messages?: Message[];
121
+ invalid_mentions?: string[];
122
+ available_mentions?: string[];
123
+ mention_lint?: {
124
+ missing_mentions: string[];
125
+ available_mentions: string[];
126
+ message: string;
127
+ };
128
+ }
129
+
130
+ export interface HeldDraftResolutionResult {
131
+ draft: HeldMessageDraft;
132
+ message?: Message;
133
+ deliveries?: MessageDelivery[];
134
+ notify?: unknown;
135
+ }
136
+
137
+ export interface ListHeldDraftsRequest {
138
+ agent?: string;
139
+ room_id?: string | null;
140
+ status?: HeldDraftStatus | 'all';
141
+ limit?: number;
142
+ }
143
+
144
+ export interface CreateRoomTasksRequest {
145
+ title?: string;
146
+ tasks?: Array<{ title: string }>;
147
+ created_by?: string | null;
148
+ source_message_id?: number | null;
149
+ }
150
+
151
+ export interface CreateRoomReminderRequest {
152
+ msg_id: number;
153
+ title: string;
154
+ created_by?: string | null;
155
+ fire_at?: string | null;
156
+ delay_seconds?: number | null;
157
+ repeat?: string | null;
158
+ }
159
+
160
+ export interface ListRoomRemindersRequest {
161
+ status?: RoomReminderStatus | 'all';
162
+ created_by?: string | null;
163
+ room_id?: string | null;
164
+ limit?: number;
165
+ }
166
+
167
+ export interface CreateRoomSavedMessageRequest {
168
+ msg_id: number;
169
+ saved_by: string;
170
+ note?: string | null;
171
+ }
172
+
173
+ export interface ListRoomSavedMessagesRequest {
174
+ saved_by?: string | null;
175
+ room_id?: string | null;
176
+ limit?: number;
177
+ }
178
+
179
+ export interface ClaimRoomTaskRequest {
180
+ assignee: string;
18
181
  }
19
182
 
20
183
  export interface DaemonAuth {
@@ -65,6 +228,15 @@ export interface ProvisionComputerRequest {
65
228
  package_name?: string;
66
229
  }
67
230
 
231
+ export interface UpdateComputerRequest {
232
+ name: string;
233
+ }
234
+
235
+ export interface RegenerateComputerCommandRequest {
236
+ server_url?: string;
237
+ package_name?: string;
238
+ }
239
+
68
240
  export interface CreateDeliveryRequest {
69
241
  message_id: number;
70
242
  agent: string;
@@ -75,6 +247,7 @@ export interface ClaimDeliveryRequest {
75
247
  connection_id?: string | null;
76
248
  computer_id?: string | null;
77
249
  lease_ms?: number;
250
+ steer_run_id?: string | null;
78
251
  }
79
252
 
80
253
  export interface GetOrCreateSessionRequest {
@@ -106,6 +279,23 @@ export interface FinishRunRequest {
106
279
  output?: string;
107
280
  }
108
281
 
282
+ export interface RecordAgentActivityRequest {
283
+ kind: AgentActivityKind;
284
+ title: string;
285
+ detail?: string | null;
286
+ metadata?: Record<string, unknown> | null;
287
+ }
288
+
289
+ export interface CreateHandoffRoomRequest {
290
+ source_room_id: string;
291
+ source_message_id?: number;
292
+ actor: string;
293
+ purpose: string;
294
+ name: string;
295
+ team?: Array<{ agent: string; mode?: AgentRoomSubscriptionMode }>;
296
+ handoff_content?: string;
297
+ }
298
+
109
299
  export interface CreateArtifactRequest {
110
300
  content_base64: string;
111
301
  mime_type: string;
@@ -132,13 +322,17 @@ export class LockClient {
132
322
  return data.chats;
133
323
  }
134
324
 
135
- async listRooms(): Promise<Chat[]> {
136
- const data = await this.get<{ rooms: Chat[] }>('/api/rooms');
325
+ async listRooms(agentScope?: string): Promise<Chat[]> {
326
+ const params = new URLSearchParams();
327
+ if (agentScope) params.set('agent_scope', agentScope);
328
+ const data = await this.get<{ rooms: Chat[] }>(`/api/rooms${params.size ? `?${params}` : ''}`);
137
329
  return data.rooms;
138
330
  }
139
331
 
140
- async listRoomMembers(room: string): Promise<{ room: Chat; participants: RoomParticipant[]; completeness: string }> {
141
- const data = await this.get<{ room: Chat; participants: RoomParticipant[]; completeness: string }>(`/api/rooms/${encodeURIComponent(room)}/members`);
332
+ async listRoomMembers(room: string, agent?: string): Promise<{ room: Chat; participants: RoomParticipant[]; agent_subscriptions?: AgentRoomSubscription[]; enabled_skills?: EnabledSkill[]; completeness: string }> {
333
+ const params = new URLSearchParams();
334
+ if (agent) params.set('agent', agent);
335
+ const data = await this.get<{ room: Chat; participants: RoomParticipant[]; agent_subscriptions?: AgentRoomSubscription[]; enabled_skills?: EnabledSkill[]; completeness: string }>(`/api/rooms/${encodeURIComponent(room)}/members${params.size ? `?${params}` : ''}`);
142
336
  return data;
143
337
  }
144
338
 
@@ -146,32 +340,183 @@ export class LockClient {
146
340
  return this.post<{ participant: RoomParticipant; subscription: unknown }>(`/api/rooms/${encodeURIComponent(room)}/agents`, input);
147
341
  }
148
342
 
343
+ async createHandoffRoom(input: CreateHandoffRoomRequest): Promise<{ room: Chat; message: Message | null; deliveries: MessageDelivery[]; notify: unknown }> {
344
+ return this.post<{ room: Chat; message: Message | null; deliveries: MessageDelivery[]; notify: unknown }>('/api/rooms/handoff', input);
345
+ }
346
+
347
+ async leaveAgentRoom(room: string, agent: string): Promise<{ room: Chat; agent: string; participant_removed: boolean; subscriptions_removed: number; active_deliveries_canceled: number }> {
348
+ return this.delete<{ room: Chat; agent: string; participant_removed: boolean; subscriptions_removed: number; active_deliveries_canceled: number }>(`/api/rooms/${encodeURIComponent(room)}/agents/${encodeURIComponent(agent)}`);
349
+ }
350
+
351
+ async deleteAgent(agent: string, input: { confirm_delete: boolean; leave_rooms: boolean; unbind_lark: boolean } = { confirm_delete: true, leave_rooms: true, unbind_lark: true }): Promise<{ deletion: AgentDeletionResult; lark: { unbound: boolean; app_id: string | null; error: string | null } }> {
352
+ return this.delete<{ deletion: AgentDeletionResult; lark: { unbound: boolean; app_id: string | null; error: string | null } }>(`/api/agents/${encodeURIComponent(agent)}`, undefined, input);
353
+ }
354
+
149
355
  async createTopic(room: string, input: { name: string; created_by?: string | null }): Promise<RoomChannel> {
150
356
  const data = await this.post<{ channel: RoomChannel }>(`/api/rooms/${encodeURIComponent(room)}/topics`, input);
151
357
  return data.channel;
152
358
  }
153
359
 
360
+ async listRoomTasks(room: string, status: RoomTaskStatus | 'all' = 'all', limit = 50): Promise<RoomTask[]> {
361
+ const params = new URLSearchParams({ status, limit: String(limit) });
362
+ const data = await this.get<{ tasks: RoomTask[] }>(`/api/rooms/${encodeURIComponent(room)}/tasks?${params}`);
363
+ return data.tasks;
364
+ }
365
+
366
+ async createRoomTasks(room: string, input: CreateRoomTasksRequest): Promise<RoomTask[]> {
367
+ const data = await this.post<{ tasks: RoomTask[] }>(`/api/rooms/${encodeURIComponent(room)}/tasks`, input);
368
+ return data.tasks;
369
+ }
370
+
371
+ async claimRoomTask(room: string, taskNumber: number, input: ClaimRoomTaskRequest): Promise<RoomTask> {
372
+ const data = await this.post<{ task: RoomTask }>(`/api/rooms/${encodeURIComponent(room)}/tasks/${taskNumber}/claim`, input);
373
+ return data.task;
374
+ }
375
+
376
+ async unclaimRoomTask(room: string, taskNumber: number): Promise<RoomTask> {
377
+ const data = await this.post<{ task: RoomTask }>(`/api/rooms/${encodeURIComponent(room)}/tasks/${taskNumber}/unclaim`, {});
378
+ return data.task;
379
+ }
380
+
381
+ async updateRoomTaskStatus(room: string, taskNumber: number, status: RoomTaskStatus): Promise<RoomTask> {
382
+ const data = await this.patch<{ task: RoomTask }>(`/api/rooms/${encodeURIComponent(room)}/tasks/${taskNumber}`, { status });
383
+ return data.task;
384
+ }
385
+
386
+ async restartRoomAgent(room: string, agent: string): Promise<AgentRun> {
387
+ const data = await this.post<{ run: AgentRun }>(`/api/rooms/${encodeURIComponent(room)}/agents/${encodeURIComponent(agent)}/restart`, {});
388
+ return data.run;
389
+ }
390
+
391
+ async scheduleReminder(input: CreateRoomReminderRequest): Promise<RoomReminder> {
392
+ const data = await this.post<{ reminder: RoomReminder }>('/api/reminders', input);
393
+ return data.reminder;
394
+ }
395
+
396
+ async listReminders(input: ListRoomRemindersRequest = {}): Promise<RoomReminder[]> {
397
+ const params = new URLSearchParams();
398
+ params.set('status', input.status ?? 'scheduled');
399
+ if (input.created_by) params.set('created_by', input.created_by);
400
+ if (input.room_id) params.set('room_id', input.room_id);
401
+ params.set('limit', String(input.limit ?? 50));
402
+ const data = await this.get<{ reminders: RoomReminder[] }>(`/api/reminders?${params}`);
403
+ return data.reminders;
404
+ }
405
+
406
+ async cancelReminder(id: string): Promise<RoomReminder> {
407
+ const data = await this.post<{ reminder: RoomReminder }>(`/api/reminders/${encodeURIComponent(id)}/cancel`, {});
408
+ return data.reminder;
409
+ }
410
+
411
+ async saveMessage(input: CreateRoomSavedMessageRequest): Promise<RoomSavedMessage> {
412
+ const data = await this.post<{ saved_message: RoomSavedMessage }>('/api/saved-messages', input);
413
+ return data.saved_message;
414
+ }
415
+
416
+ async listSavedMessages(input: ListRoomSavedMessagesRequest = {}): Promise<RoomSavedMessage[]> {
417
+ const params = new URLSearchParams();
418
+ if (input.saved_by) params.set('saved_by', input.saved_by);
419
+ if (input.room_id) params.set('room_id', input.room_id);
420
+ params.set('limit', String(input.limit ?? 50));
421
+ const data = await this.get<{ saved_messages: RoomSavedMessage[] }>(`/api/saved-messages?${params}`);
422
+ return data.saved_messages;
423
+ }
424
+
425
+ async removeSavedMessage(id: string): Promise<RoomSavedMessage> {
426
+ const data = await this.post<{ saved_message: RoomSavedMessage }>(`/api/saved-messages/${encodeURIComponent(id)}/delete`, {});
427
+ return data.saved_message;
428
+ }
429
+
154
430
  async getMessages(params: URLSearchParams): Promise<Message[]> {
155
431
  const data = await this.get<{ messages: Message[] }>(`/api/messages?${params}`);
156
432
  return data.messages;
157
433
  }
158
434
 
435
+ async getDeliveryContext(input: { agent: string; chatId: string; messageId: number; limit?: number }): Promise<DeliveryContext> {
436
+ const params = new URLSearchParams({
437
+ agent: input.agent,
438
+ chat_id: input.chatId,
439
+ message_id: String(input.messageId),
440
+ limit: String(input.limit ?? 50),
441
+ });
442
+ const data = await this.get<{ context: DeliveryContext }>(`/api/messages/context?${params}`);
443
+ return data.context;
444
+ }
445
+
159
446
  async getInbox(agent: string, after = 0, limit = 50): Promise<Message[]> {
160
447
  const params = new URLSearchParams({ agent, after: String(after), limit: String(limit) });
161
448
  const data = await this.get<{ messages: Message[] }>(`/api/inbox?${params}`);
162
449
  return data.messages;
163
450
  }
164
451
 
165
- async getMessage(id: number): Promise<Message> {
166
- const data = await this.get<{ message: Message }>(`/api/messages/${id}`);
452
+ async getMessage(id: number, agentScope?: string): Promise<Message> {
453
+ const suffix = agentScope ? `?agent_scope=${encodeURIComponent(agentScope)}` : '';
454
+ const data = await this.get<{ message: Message }>(`/api/messages/${id}${suffix}`);
167
455
  return data.message;
168
456
  }
169
457
 
458
+ async getMessageAttachmentContent(id: string, agentScope?: string): Promise<{ content: Uint8Array; mimeType: string; filename: string }> {
459
+ const suffix = agentScope ? `?agent_scope=${encodeURIComponent(agentScope)}` : '';
460
+ const response = await fetch(`${this.baseUrl}/api/message-attachments/${encodeURIComponent(id)}/content${suffix}`);
461
+ if (!response.ok) {
462
+ let message = `request failed: ${response.status}`;
463
+ let code = 'REQUEST_FAILED';
464
+ try {
465
+ const payload = await response.json() as ApiResponse<unknown>;
466
+ if ('code' in payload && typeof payload.code === 'string') code = payload.code;
467
+ message = payload.message ?? message;
468
+ } catch {
469
+ // Binary endpoint errors should be JSON, but keep a fallback.
470
+ }
471
+ throw new HttpError(response.status, code, message);
472
+ }
473
+ const filename = response.headers.get('content-disposition')?.match(/filename="([^"]+)"/)?.[1] ?? id;
474
+ return {
475
+ content: new Uint8Array(await response.arrayBuffer()),
476
+ mimeType: response.headers.get('content-type') ?? 'application/octet-stream',
477
+ filename,
478
+ };
479
+ }
480
+
170
481
  async sendMessage(input: SendRequest): Promise<Message> {
171
- const data = await this.post<{ message: Message }>('/api/messages', input);
482
+ const data = await this.sendMessageResult(input);
483
+ if (!data.message) throw new HttpError(202, 'MESSAGE_HELD', 'message was held as a draft');
172
484
  return data.message;
173
485
  }
174
486
 
487
+ async sendMessageResult(input: SendRequest): Promise<SendMessageResult> {
488
+ return this.post<SendMessageResult>('/api/messages', input);
489
+ }
490
+
491
+ async getAgentWorkbench(agent: string): Promise<AgentWorkbench> {
492
+ const data = await this.get<{ workbench: AgentWorkbench }>(`/api/agents/${encodeURIComponent(agent)}/workbench`);
493
+ return data.workbench;
494
+ }
495
+
496
+ async listHeldDrafts(input: ListHeldDraftsRequest = {}): Promise<HeldMessageDraft[]> {
497
+ const agent = input.agent?.trim();
498
+ if (!agent) throw new Error('agent is required');
499
+ const workbench = await this.getAgentWorkbench(agent);
500
+ const status = input.status ?? 'held';
501
+ const limit = input.limit ?? 50;
502
+ return workbench.held_drafts
503
+ .filter((draft) => status === 'all' || draft.status === status)
504
+ .filter((draft) => !input.room_id || draft.chat_id === input.room_id)
505
+ .slice(0, limit);
506
+ }
507
+
508
+ async abandonHeldDraft(id: string): Promise<HeldDraftResolutionResult> {
509
+ return this.post<HeldDraftResolutionResult>(`/api/held-drafts/${encodeURIComponent(id)}/abandon`, {});
510
+ }
511
+
512
+ async sendHeldDraftAnyway(id: string): Promise<HeldDraftResolutionResult> {
513
+ return this.post<HeldDraftResolutionResult>(`/api/held-drafts/${encodeURIComponent(id)}/send-anyway`, {});
514
+ }
515
+
516
+ async reviseHeldDraft(id: string, content: string): Promise<HeldDraftResolutionResult> {
517
+ return this.post<HeldDraftResolutionResult>(`/api/held-drafts/${encodeURIComponent(id)}/revise`, { content });
518
+ }
519
+
175
520
  async provisionComputer(input: ProvisionComputerRequest = {}): Promise<ProvisionedComputer> {
176
521
  const data = await this.post<ProvisionedComputer>('/api/computers/provision', input);
177
522
  return data;
@@ -182,6 +527,29 @@ export class LockClient {
182
527
  return data.computers;
183
528
  }
184
529
 
530
+ async updateComputer(computerId: string, input: UpdateComputerRequest): Promise<Computer> {
531
+ const data = await this.patch<{ computer: Computer }>(`/api/computers/${encodeURIComponent(computerId)}`, input);
532
+ return data.computer;
533
+ }
534
+
535
+ async regenerateComputerCommand(computerId: string, input: RegenerateComputerCommandRequest = {}): Promise<ProvisionedComputer> {
536
+ return this.post<ProvisionedComputer>(`/api/computers/${encodeURIComponent(computerId)}/reconnect-command`, input);
537
+ }
538
+
539
+ async getComputerDeleteImpact(computerId: string): Promise<ComputerDeletionImpact> {
540
+ const data = await this.get<{ impact: ComputerDeletionImpact }>(`/api/computers/${encodeURIComponent(computerId)}/delete-impact`);
541
+ return data.impact;
542
+ }
543
+
544
+ async deleteComputer(computerId: string, input: { remove_projects?: boolean; remove_agents?: boolean } = {}): Promise<ComputerDeletionResult & { deleted: boolean }> {
545
+ return this.delete<ComputerDeletionResult & { deleted: boolean }>(`/api/computers/${encodeURIComponent(computerId)}`, undefined, input);
546
+ }
547
+
548
+ async fetchPermissionProfile(agent: string): Promise<AgentPermissionProfile> {
549
+ const data = await this.get<{ profile: AgentPermissionProfile }>(`/api/agents/${encodeURIComponent(agent)}/permissions`);
550
+ return data.profile;
551
+ }
552
+
185
553
  async connectComputer(input: ConnectComputerRequest): Promise<{ computer: Computer; connection: ComputerConnection; token: string; local_control_token?: string; daemon: DaemonInstance; agents: ComputerAgentAssignment[] }> {
186
554
  const data = await this.post<{ computer: Computer; connection: ComputerConnection; token: string; local_control_token?: string; daemon: DaemonInstance; agents: ComputerAgentAssignment[] }>('/api/computers/connect', input);
187
555
  return data;
@@ -205,12 +573,80 @@ export class LockClient {
205
573
  return data.daemon;
206
574
  }
207
575
 
208
- async listDeliveries(agent: string, status = 'pending', limit = 50): Promise<MessageDelivery[]> {
576
+ async listDeliveries(agent: string, status = 'pending', limit = 50, options: { distinctChat?: boolean; excludeRunningComputerId?: string | null; connectionId?: string | null } = {}): Promise<MessageDelivery[]> {
209
577
  const params = new URLSearchParams({ agent, status, limit: String(limit) });
578
+ if (options.distinctChat) params.set('distinct_chat', 'true');
579
+ if (options.excludeRunningComputerId) params.set('exclude_running_computer_id', options.excludeRunningComputerId);
580
+ if (options.connectionId) params.set('connection_id', options.connectionId);
210
581
  const data = await this.get<{ deliveries: MessageDelivery[] }>(`/api/deliveries?${params}`);
211
582
  return data.deliveries;
212
583
  }
213
584
 
585
+ async getDelivery(id: string): Promise<MessageDelivery> {
586
+ const data = await this.get<{ delivery: MessageDelivery }>(`/api/deliveries/${encodeURIComponent(id)}`);
587
+ return data.delivery;
588
+ }
589
+
590
+ async listPendingDeliveryAgents(): Promise<Array<{ agent: string; pending: number }>> {
591
+ const data = await this.get<{ agents: Array<{ agent: string; pending: number }> }>('/api/deliveries/pending-agents');
592
+ return data.agents;
593
+ }
594
+
595
+ async listDeliveryBacklog(): Promise<DeliveryBacklogSummary[]> {
596
+ const data = await this.get<{ backlog: DeliveryBacklogSummary[] }>('/api/deliveries/backlog');
597
+ return data.backlog;
598
+ }
599
+
600
+ async getDeliveryWebSocketStatus(): Promise<DeliveryConnectionStatus> {
601
+ const data = await this.get<{ websocket: DeliveryConnectionStatus }>('/api/daemon/ws/status');
602
+ return data.websocket;
603
+ }
604
+
605
+ async getMessagingStatus(): Promise<MessagingStatus> {
606
+ return this.get<MessagingStatus>('/api/messaging/status');
607
+ }
608
+
609
+ async getMessagingHealth(): Promise<MessagingHealth> {
610
+ return this.get<MessagingHealth>('/api/messaging/health');
611
+ }
612
+
613
+ async probeDelivery(input: { agent?: string; room?: string; sender?: string; content?: string; idempotencyKey?: string | null } = {}, token = defaultServerToken()): Promise<DeliveryProbeResult> {
614
+ return this.post<DeliveryProbeResult>('/api/messaging/probe-delivery', {
615
+ agent: input.agent,
616
+ room: input.room,
617
+ sender: input.sender,
618
+ content: input.content,
619
+ idempotency_key: input.idempotencyKey,
620
+ }, serverAuthHeaders(token));
621
+ }
622
+
623
+ async restartLarkWebSocket(options: { appId?: string; appIds?: string[] } = {}): Promise<LarkRestartResult> {
624
+ const body = options.appId ? { app_id: options.appId } : options.appIds ? { app_ids: options.appIds } : {};
625
+ return this.post<LarkRestartResult>('/api/lark/restart', body);
626
+ }
627
+
628
+ async probeLarkEvent(input: { appId: string; envelope?: string; data: unknown }, token = defaultServerToken()): Promise<LarkProbeEventResult> {
629
+ return this.post<LarkProbeEventResult>('/api/lark/probe-event', {
630
+ app_id: input.appId,
631
+ envelope: input.envelope,
632
+ data: input.data,
633
+ }, serverAuthHeaders(token));
634
+ }
635
+
636
+ async listRecentLarkEvents(limit = 20): Promise<LarkRecentEventSummary[]> {
637
+ const params = new URLSearchParams({ limit: String(limit) });
638
+ const data = await this.get<{ events: LarkRecentEventSummary[] }>(`/api/lark/events/recent?${params}`);
639
+ return data.events;
640
+ }
641
+
642
+ async repairLarkEventParseFailures(input: { appId?: string; limit?: number; dryRun?: boolean } = {}, token = defaultServerToken()): Promise<LarkRepairParseResult> {
643
+ return this.post<LarkRepairParseResult>('/api/lark/events/repair-parse', {
644
+ app_id: input.appId,
645
+ limit: input.limit,
646
+ dry_run: input.dryRun,
647
+ }, serverAuthHeaders(token));
648
+ }
649
+
214
650
  async createDelivery(input: CreateDeliveryRequest): Promise<MessageDelivery> {
215
651
  const data = await this.post<{ delivery: MessageDelivery }>('/api/deliveries', input);
216
652
  return data.delivery;
@@ -226,6 +662,11 @@ export class LockClient {
226
662
  return data.delivery;
227
663
  }
228
664
 
665
+ async markDeliveryProcessingCompleted(id: string, input: FinishDeliveryRequest): Promise<MessageDelivery> {
666
+ const data = await this.post<{ delivery: MessageDelivery }>(`/api/deliveries/${id}/processing-completed`, input);
667
+ return data.delivery;
668
+ }
669
+
229
670
  async getOrCreateSession(input: GetOrCreateSessionRequest): Promise<AgentSession> {
230
671
  const data = await this.post<{ session: AgentSession }>('/api/sessions', input);
231
672
  return data.session;
@@ -271,6 +712,11 @@ export class LockClient {
271
712
  return data.run;
272
713
  }
273
714
 
715
+ async recordRunActivity(runId: string, input: RecordAgentActivityRequest): Promise<AgentActivityEvent> {
716
+ const data = await this.post<{ event: AgentActivityEvent }>(`/api/runs/${runId}/activity`, input);
717
+ return data.event;
718
+ }
719
+
274
720
  async finishRun(runId: string, input: FinishRunRequest): Promise<AgentRun> {
275
721
  const data = await this.post<{ run: AgentRun }>(`/api/runs/${runId}/finish`, input);
276
722
  return data.run;
@@ -298,6 +744,22 @@ export class LockClient {
298
744
  });
299
745
  }
300
746
 
747
+ private async patch<T>(path: string, body: unknown, headers?: HeadersInit): Promise<T> {
748
+ return this.request<T>(path, {
749
+ method: 'PATCH',
750
+ headers: { 'content-type': 'application/json', ...this.daemonAuthHeaders(), ...headers },
751
+ body: JSON.stringify(body),
752
+ });
753
+ }
754
+
755
+ private async delete<T>(path: string, headers?: HeadersInit, body?: unknown): Promise<T> {
756
+ return this.request<T>(path, {
757
+ method: 'DELETE',
758
+ headers: { ...(body === undefined ? {} : { 'content-type': 'application/json' }), ...this.daemonAuthHeaders(), ...headers },
759
+ body: body === undefined ? undefined : JSON.stringify(body),
760
+ });
761
+ }
762
+
301
763
  private daemonAuthHeaders(): HeadersInit {
302
764
  if (!this.daemonAuth) return {};
303
765
  return {
@@ -311,7 +773,7 @@ export class LockClient {
311
773
  const response = await fetch(`${this.baseUrl}${path}`, init);
312
774
  const payload = await response.json() as ApiResponse<T>;
313
775
  if (!response.ok || !payload.ok) {
314
- throw new Error(payload.message ?? `request failed: ${response.status}`);
776
+ throw new HttpError(response.status, 'code' in payload && typeof payload.code === 'string' ? payload.code : 'REQUEST_FAILED', payload.message ?? `request failed: ${response.status}`);
315
777
  }
316
778
  return payload.data as T;
317
779
  }
package/src/coco.ts CHANGED
@@ -1,52 +1,21 @@
1
1
  import { buildPalPrompt, runtimeCwd, type AgentRuntime, type AgentRuntimeRunInput } from './agent-runtime.js';
2
+ import { buildCocoPermissionArgs, buildRuntimeLaunchContext, effectivePermissionProfile } from './agent-permissions.js';
2
3
  export { runAgentRuntime } from './agent-runtime.js';
3
4
 
4
- function extractSessionId(stdout: string): string | null {
5
- for (const line of stdout.split(/\r?\n/)) {
6
- const trimmed = line.trim();
7
- if (!trimmed.startsWith('{')) continue;
8
- try {
9
- const event = JSON.parse(trimmed) as { session_id?: unknown };
10
- if (typeof event.session_id === 'string' && event.session_id.trim()) {
11
- return event.session_id;
12
- }
13
- } catch {
14
- // Non-JSON stdout is kept in output; it just is not a JSONL event.
15
- }
16
- }
17
- return null;
18
- }
19
-
20
- export function makeCocoRuntime(_agentUuid: string): AgentRuntime {
5
+ export function makeCocoRuntime(_agentUuid: string, model?: string | null): AgentRuntime {
6
+ // Coco ACP exits before initialize when passed --model. Per-invocation model
7
+ // selection works through config overrides.
8
+ const modelArgs = model?.trim() ? ['-c', `model.name=${model.trim()}`] : [];
21
9
  return {
22
10
  name: 'coco',
23
- capabilities: { protocol: 'acp', resume: 'runtime-session-id', busyDeliveryMode: 'queue', supportsMcp: true },
24
- command: 'coco',
25
- buildPrompt: buildPalPrompt,
26
- buildCwd: runtimeCwd,
27
- buildArgs(input: AgentRuntimeRunInput): string[] {
28
- return ['acp', 'serve', '-y', ...input.extraArgs];
29
- },
30
- };
31
- }
32
-
33
- export function makeCocoStreamJsonRuntime(_agentUuid: string): AgentRuntime {
34
- return {
35
- name: 'coco-stream-json',
36
- capabilities: { protocol: 'json-stream', resume: 'runtime-session-id', busyDeliveryMode: 'queue', supportsMcp: false },
11
+ capabilities: { protocol: 'acp', resume: 'runtime-session-id', busyDeliveryMode: 'queue', supportsMcp: true, supportsSteer: false },
37
12
  command: 'coco',
38
13
  buildPrompt: buildPalPrompt,
39
14
  buildCwd: runtimeCwd,
40
15
  buildArgs(input: AgentRuntimeRunInput): string[] {
41
- const prompt = buildPalPrompt(input);
42
- const resumeArgs = input.runtimeSessionId ? ['--resume', input.runtimeSessionId] : [];
43
- return ['-y', '-p', '--output-format', 'stream-json', ...resumeArgs, ...input.extraArgs, prompt];
44
- },
45
- parseOutput({ stdout, stderr, input }) {
46
- return {
47
- output: [stdout.trim(), stderr.trim()].filter(Boolean).join('\n'),
48
- runtimeSessionId: extractSessionId(stdout) ?? input.runtimeSessionId ?? null,
49
- };
16
+ const context = input.launchContext ?? buildRuntimeLaunchContext(input);
17
+ const permissionArgs = buildCocoPermissionArgs(effectivePermissionProfile(input), context);
18
+ return ['acp', 'serve', ...permissionArgs, ...modelArgs, ...input.extraArgs];
50
19
  },
51
20
  };
52
21
  }