@controlflow-ai/daemon 0.1.1 → 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 (65) hide show
  1. package/README.md +66 -24
  2. package/package.json +16 -3
  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 +810 -28
  7. package/src/agent-workspace.ts +183 -0
  8. package/src/app.ts +2183 -79
  9. package/src/args.ts +54 -7
  10. package/src/cli.ts +873 -14
  11. package/src/client.ts +482 -12
  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 +460 -26
  16. package/src/daemon-client.ts +116 -3
  17. package/src/daemon.ts +958 -101
  18. package/src/db.ts +3216 -113
  19. package/src/delivery-ws.ts +269 -0
  20. package/src/format.ts +4 -1
  21. package/src/lark/app-registration.ts +141 -0
  22. package/src/lark/cli.ts +7 -137
  23. package/src/lark/credentials.ts +36 -3
  24. package/src/lark/event-router.ts +61 -5
  25. package/src/lark/inbound-events.ts +156 -3
  26. package/src/lark/server-integration.ts +659 -111
  27. package/src/lark/setup.ts +74 -5
  28. package/src/lark/ws-daemon.ts +136 -10
  29. package/src/local-api.ts +611 -14
  30. package/src/local-auth.ts +36 -3
  31. package/src/message-attachments.ts +71 -0
  32. package/src/messaging-cli.ts +741 -0
  33. package/src/messaging-status.ts +669 -0
  34. package/src/migrations/023_projects.ts +65 -0
  35. package/src/migrations/024_agents_model.ts +10 -0
  36. package/src/migrations/025_room_archive.ts +44 -0
  37. package/src/migrations/026_project_archive.ts +44 -0
  38. package/src/migrations/027_agent_permission_profiles.ts +16 -0
  39. package/src/migrations/028_lark_websocket_restart_state.ts +16 -0
  40. package/src/migrations/029_held_message_drafts.ts +32 -0
  41. package/src/migrations/030_agent_room_read_state.ts +25 -0
  42. package/src/migrations/031_room_tasks.ts +29 -0
  43. package/src/migrations/032_room_reminders.ts +29 -0
  44. package/src/migrations/033_room_saved_messages.ts +25 -0
  45. package/src/migrations/034_agent_activity_events.ts +27 -0
  46. package/src/migrations/035_agent_avatars.ts +17 -0
  47. package/src/migrations/036_project_agent_defaults.ts +21 -0
  48. package/src/migrations/037_message_attachments.ts +36 -0
  49. package/src/migrations/038_agent_activity_room_scope.ts +64 -0
  50. package/src/migrations/039_message_attachments_path.ts +34 -0
  51. package/src/migrations/040_message_attachments_file_schema.ts +80 -0
  52. package/src/migrations/041_room_system_events.ts +30 -0
  53. package/src/migrations/042_message_attachment_file_kind.ts +52 -0
  54. package/src/migrations/043_room_mode_skill_registry.ts +92 -0
  55. package/src/migrations/044_workflow_runtime.ts +69 -0
  56. package/src/migrations/045_skill_repository_ownership.ts +64 -0
  57. package/src/migrations.ts +70 -1
  58. package/src/neeko.ts +40 -4
  59. package/src/runtime-env.ts +179 -0
  60. package/src/runtime-registry.ts +83 -13
  61. package/src/server.ts +244 -4
  62. package/src/token-file.ts +13 -6
  63. package/src/types.ts +394 -0
  64. package/src/workflow-runtime.ts +275 -0
  65. 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, 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,8 +527,39 @@ export class LockClient {
182
527
  return data.computers;
183
528
  }
184
529
 
185
- async connectComputer(input: ConnectComputerRequest): Promise<{ computer: Computer; connection: ComputerConnection; token: string; daemon: DaemonInstance; agents: ComputerAgentAssignment[] }> {
186
- const data = await this.post<{ computer: Computer; connection: ComputerConnection; token: string; daemon: DaemonInstance; agents: ComputerAgentAssignment[] }>('/api/computers/connect', input);
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
+
553
+ async connectComputer(input: ConnectComputerRequest): Promise<{ computer: Computer; connection: ComputerConnection; token: string; local_control_token?: string; daemon: DaemonInstance; agents: ComputerAgentAssignment[] }> {
554
+ const data = await this.post<{ computer: Computer; connection: ComputerConnection; token: string; local_control_token?: string; daemon: DaemonInstance; agents: ComputerAgentAssignment[] }>('/api/computers/connect', input);
555
+ return data;
556
+ }
557
+
558
+ async listComputerFiles(computerId: string, input: { path?: string; show_hidden?: boolean } = {}): Promise<RemoteFileList> {
559
+ const params = new URLSearchParams();
560
+ if (input.path) params.set('path', input.path);
561
+ if (input.show_hidden) params.set('show_hidden', 'true');
562
+ const data = await this.get<RemoteFileList>(`/api/computers/${encodeURIComponent(computerId)}/files${params.size ? `?${params}` : ''}`);
187
563
  return data;
188
564
  }
189
565
 
@@ -197,12 +573,80 @@ export class LockClient {
197
573
  return data.daemon;
198
574
  }
199
575
 
200
- 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[]> {
201
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);
202
581
  const data = await this.get<{ deliveries: MessageDelivery[] }>(`/api/deliveries?${params}`);
203
582
  return data.deliveries;
204
583
  }
205
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
+
206
650
  async createDelivery(input: CreateDeliveryRequest): Promise<MessageDelivery> {
207
651
  const data = await this.post<{ delivery: MessageDelivery }>('/api/deliveries', input);
208
652
  return data.delivery;
@@ -218,6 +662,11 @@ export class LockClient {
218
662
  return data.delivery;
219
663
  }
220
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
+
221
670
  async getOrCreateSession(input: GetOrCreateSessionRequest): Promise<AgentSession> {
222
671
  const data = await this.post<{ session: AgentSession }>('/api/sessions', input);
223
672
  return data.session;
@@ -263,6 +712,11 @@ export class LockClient {
263
712
  return data.run;
264
713
  }
265
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
+
266
720
  async finishRun(runId: string, input: FinishRunRequest): Promise<AgentRun> {
267
721
  const data = await this.post<{ run: AgentRun }>(`/api/runs/${runId}/finish`, input);
268
722
  return data.run;
@@ -290,6 +744,22 @@ export class LockClient {
290
744
  });
291
745
  }
292
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
+
293
763
  private daemonAuthHeaders(): HeadersInit {
294
764
  if (!this.daemonAuth) return {};
295
765
  return {
@@ -303,7 +773,7 @@ export class LockClient {
303
773
  const response = await fetch(`${this.baseUrl}${path}`, init);
304
774
  const payload = await response.json() as ApiResponse<T>;
305
775
  if (!response.ok || !payload.ok) {
306
- 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}`);
307
777
  }
308
778
  return payload.data as T;
309
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
  }