@controlflow-ai/daemon 0.1.2 → 0.1.4

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 (62) hide show
  1. package/README.md +54 -6
  2. package/bin/daemon.js +6 -1
  3. package/package.json +3 -1
  4. package/src/agent-avatar.ts +30 -0
  5. package/src/agent-key.ts +28 -0
  6. package/src/agent-permissions.ts +359 -0
  7. package/src/agent-runtime.ts +795 -28
  8. package/src/agent-workspace.ts +183 -0
  9. package/src/app.ts +1970 -79
  10. package/src/args.ts +54 -7
  11. package/src/cli.ts +873 -14
  12. package/src/client.ts +472 -10
  13. package/src/coco.ts +9 -40
  14. package/src/codex.ts +33 -5
  15. package/src/config.ts +28 -4
  16. package/src/console.ts +230 -20
  17. package/src/daemon-client.ts +116 -3
  18. package/src/daemon.ts +937 -99
  19. package/src/db.ts +3128 -122
  20. package/src/delivery-ws.ts +269 -0
  21. package/src/format.ts +4 -1
  22. package/src/lark/cli.ts +3 -3
  23. package/src/lark/event-router.ts +60 -4
  24. package/src/lark/inbound-events.ts +156 -3
  25. package/src/lark/server-integration.ts +659 -111
  26. package/src/lark/ws-daemon.ts +136 -10
  27. package/src/local-api.ts +545 -15
  28. package/src/local-auth.ts +33 -1
  29. package/src/message-attachments.ts +71 -0
  30. package/src/messaging-cli.ts +741 -0
  31. package/src/messaging-status.ts +669 -0
  32. package/src/migrations/024_agents_model.ts +10 -0
  33. package/src/migrations/025_room_archive.ts +44 -0
  34. package/src/migrations/026_project_archive.ts +44 -0
  35. package/src/migrations/027_agent_permission_profiles.ts +16 -0
  36. package/src/migrations/028_lark_websocket_restart_state.ts +16 -0
  37. package/src/migrations/029_held_message_drafts.ts +32 -0
  38. package/src/migrations/030_agent_room_read_state.ts +25 -0
  39. package/src/migrations/031_room_tasks.ts +29 -0
  40. package/src/migrations/032_room_reminders.ts +29 -0
  41. package/src/migrations/033_room_saved_messages.ts +25 -0
  42. package/src/migrations/034_agent_activity_events.ts +27 -0
  43. package/src/migrations/035_agent_avatars.ts +17 -0
  44. package/src/migrations/036_project_agent_defaults.ts +21 -0
  45. package/src/migrations/037_message_attachments.ts +36 -0
  46. package/src/migrations/038_agent_activity_room_scope.ts +64 -0
  47. package/src/migrations/039_message_attachments_path.ts +34 -0
  48. package/src/migrations/040_message_attachments_file_schema.ts +80 -0
  49. package/src/migrations/041_room_system_events.ts +30 -0
  50. package/src/migrations/042_message_attachment_file_kind.ts +52 -0
  51. package/src/migrations/043_room_mode_skill_registry.ts +92 -0
  52. package/src/migrations/044_workflow_runtime.ts +69 -0
  53. package/src/migrations/045_skill_repository_ownership.ts +64 -0
  54. package/src/migrations.ts +69 -1
  55. package/src/neeko.ts +40 -4
  56. package/src/runtime-env.ts +179 -0
  57. package/src/runtime-registry.ts +83 -13
  58. package/src/server.ts +244 -4
  59. package/src/token-file.ts +13 -6
  60. package/src/types.ts +362 -0
  61. package/src/workflow-runtime.ts +275 -0
  62. package/src/web.ts +0 -904
@@ -10,13 +10,22 @@ export type LarkEventCallback = (event: {
10
10
  storeResult: StoreInboundEventResult;
11
11
  }) => void | Promise<void>;
12
12
 
13
+ export type LarkWebSocketLifecycleCallback = (event: {
14
+ appId: string;
15
+ type: 'ready' | 'reconnecting' | 'reconnected' | 'error';
16
+ error?: string;
17
+ }) => void;
18
+
13
19
  export interface StartLarkDaemonOptions {
14
20
  appId: string;
15
21
  appSecret: string;
16
22
  db: Database;
17
23
  onEvent?: LarkEventCallback;
24
+ onLifecycle?: LarkWebSocketLifecycleCallback;
18
25
  logger?: Partial<Pick<Console, 'info' | 'warn' | 'error'>>;
19
26
  loggerLevel?: Lark.LoggerLevel;
27
+ handshakeTimeoutMs?: number;
28
+ pingTimeoutSec?: number;
20
29
  }
21
30
 
22
31
  export interface LarkDaemonHandle {
@@ -26,6 +35,44 @@ export interface LarkDaemonHandle {
26
35
  }
27
36
 
28
37
  const SUPPORTED_EVENTS = ['im.message.receive_v1', 'card.action.trigger'] as const;
38
+ const silentLarkLogger = {
39
+ error() {},
40
+ warn() {},
41
+ info() {},
42
+ debug() {},
43
+ trace() {},
44
+ };
45
+
46
+ function nestedString(value: unknown, path: string[]): string | null {
47
+ let current = value;
48
+ for (const key of path) {
49
+ if (!current || typeof current !== 'object') return null;
50
+ current = (current as Record<string, unknown>)[key];
51
+ }
52
+ return typeof current === 'string' && current.length > 0 ? current : null;
53
+ }
54
+
55
+ export function stableLarkEventId(eventName: string, data: unknown): string | null {
56
+ const headerId = nestedString(data, ['header', 'event_id'])
57
+ ?? nestedString(data, ['event_id']);
58
+ if (headerId) return headerId;
59
+ const derivedId = nestedString(data, ['message', 'message_id'])
60
+ ?? nestedString(data, ['message', 'open_message_id'])
61
+ ?? nestedString(data, ['open_message_id']);
62
+ if (derivedId) return `${eventName}:${derivedId}`;
63
+ const operatorId = nestedString(data, ['operator', 'operator_id', 'open_id']);
64
+ if (operatorId) return `${eventName}:${operatorId}:${nestedString(data, ['token']) ?? ''}`;
65
+ return null;
66
+ }
67
+
68
+ export function buildLarkEventRawBody(eventName: string, data: unknown): string {
69
+ const eventId = stableLarkEventId(eventName, data);
70
+ return JSON.stringify({
71
+ schema: '2.0',
72
+ header: eventId ? { event_id: eventId, event_type: eventName } : { event_type: eventName },
73
+ event: data,
74
+ });
75
+ }
29
76
 
30
77
  /**
31
78
  * Start a Lark WSClient that subscribes to im.message.receive_v1 +
@@ -45,7 +92,7 @@ export function startLarkDaemon(options: StartLarkDaemonOptions): LarkDaemonHand
45
92
  const handlers: Record<string, (data: unknown) => Promise<unknown>> = {};
46
93
  for (const eventName of SUPPORTED_EVENTS) {
47
94
  handlers[eventName] = async (data) => {
48
- const rawBody = JSON.stringify({ schema: '2.0', header: { event_type: eventName }, event: data });
95
+ const rawBody = buildLarkEventRawBody(eventName, data);
49
96
  try {
50
97
  const storeResult = await storeInboundEvent(options.db, { appId: options.appId, rawBody });
51
98
  if (storeResult.duplicate) {
@@ -75,6 +122,24 @@ export function startLarkDaemon(options: StartLarkDaemonOptions): LarkDaemonHand
75
122
  appId: options.appId,
76
123
  appSecret: options.appSecret,
77
124
  loggerLevel: options.loggerLevel ?? Lark.LoggerLevel.warn,
125
+ handshakeTimeoutMs: options.handshakeTimeoutMs ?? 15_000,
126
+ wsConfig: { pingTimeout: options.pingTimeoutSec ?? 10 },
127
+ onReady: () => {
128
+ options.onLifecycle?.({ appId: options.appId, type: 'ready' });
129
+ log.info?.(`[lark/${options.appId}] WSClient ready`);
130
+ },
131
+ onReconnecting: () => {
132
+ options.onLifecycle?.({ appId: options.appId, type: 'reconnecting' });
133
+ log.warn?.(`[lark/${options.appId}] WSClient reconnecting`);
134
+ },
135
+ onReconnected: () => {
136
+ options.onLifecycle?.({ appId: options.appId, type: 'reconnected' });
137
+ log.info?.(`[lark/${options.appId}] WSClient reconnected`);
138
+ },
139
+ onError: (err) => {
140
+ options.onLifecycle?.({ appId: options.appId, type: 'error', error: err.message });
141
+ log.error?.(`[lark/${options.appId}] WSClient error: ${err.message}`);
142
+ },
78
143
  });
79
144
 
80
145
  wsClient.start({ eventDispatcher });
@@ -103,35 +168,96 @@ export function createLarkApiClient(appId: string, appSecret: string): Lark.Clie
103
168
  return new Lark.Client({
104
169
  appId,
105
170
  appSecret,
106
- loggerLevel: Lark.LoggerLevel.warn,
171
+ loggerLevel: Lark.LoggerLevel.fatal,
172
+ logger: silentLarkLogger,
107
173
  });
108
174
  }
109
175
 
110
- export interface SendTextMessageInput {
176
+ export type LarkReceiveIdType = 'chat_id' | 'open_id' | 'union_id' | 'email' | 'user_id';
177
+
178
+ export interface LarkMentionTarget {
179
+ openId: string;
180
+ displayName?: string | null;
181
+ }
182
+
183
+ interface SendMessageBaseInput {
111
184
  client: Lark.Client;
112
- receiveIdType: 'chat_id' | 'open_id' | 'union_id' | 'email' | 'user_id';
185
+ receiveIdType: LarkReceiveIdType;
113
186
  receiveId: string;
114
187
  text: string;
188
+ mention?: LarkMentionTarget | null;
115
189
  }
116
190
 
191
+ export interface SendTextMessageInput extends SendMessageBaseInput {}
192
+
193
+ export interface SendCardMessageInput extends SendMessageBaseInput {}
194
+
117
195
  export interface SendTextMessageResult {
118
196
  messageId?: string;
119
197
  raw: unknown;
120
198
  }
121
199
 
122
- export async function sendTextMessage(input: SendTextMessageInput): Promise<SendTextMessageResult> {
200
+ export interface LarkMessageCreatePayload {
201
+ receive_id: string;
202
+ msg_type: 'text' | 'interactive';
203
+ content: string;
204
+ }
205
+
206
+ function escapeLarkText(value: string): string {
207
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
208
+ }
209
+
210
+ function larkMentionSuffix(mention: LarkMentionTarget | null | undefined, tag: 'text' | 'card'): string {
211
+ if (!mention?.openId) return '';
212
+ const idAttribute = tag === 'text' ? 'user_id' : 'id';
213
+ return `\n\n<at ${idAttribute}="${mention.openId}">${escapeLarkText(mention.displayName ?? '')}</at>`;
214
+ }
215
+
216
+ function larkMessageBody(input: Pick<SendMessageBaseInput, 'text' | 'mention'>, tag: 'text' | 'card'): string {
217
+ return `${input.text}${larkMentionSuffix(input.mention, tag)}`;
218
+ }
219
+
220
+ export function buildLarkTextMessagePayload(input: Pick<SendTextMessageInput, 'receiveId' | 'text' | 'mention'>): LarkMessageCreatePayload {
221
+ return {
222
+ receive_id: input.receiveId,
223
+ msg_type: 'text',
224
+ content: JSON.stringify({ text: larkMessageBody(input, 'text') }),
225
+ };
226
+ }
227
+
228
+ export function buildLarkMentionCardPayload(input: Pick<SendCardMessageInput, 'receiveId' | 'text' | 'mention'>): LarkMessageCreatePayload {
229
+ return {
230
+ receive_id: input.receiveId,
231
+ msg_type: 'interactive',
232
+ content: JSON.stringify({
233
+ config: { wide_screen_mode: true },
234
+ elements: [
235
+ {
236
+ tag: 'markdown',
237
+ content: larkMessageBody(input, 'card'),
238
+ },
239
+ ],
240
+ }),
241
+ };
242
+ }
243
+
244
+ async function createLarkMessage(input: SendMessageBaseInput, payload: LarkMessageCreatePayload): Promise<SendTextMessageResult> {
123
245
  const response = await input.client.im.message.create({
124
246
  params: { receive_id_type: input.receiveIdType },
125
- data: {
126
- receive_id: input.receiveId,
127
- msg_type: 'text',
128
- content: JSON.stringify({ text: input.text }),
129
- },
247
+ data: payload,
130
248
  });
131
249
  const messageId = (response?.data as { message_id?: string } | undefined)?.message_id;
132
250
  return { messageId, raw: response };
133
251
  }
134
252
 
253
+ export async function sendTextMessage(input: SendTextMessageInput): Promise<SendTextMessageResult> {
254
+ return createLarkMessage(input, buildLarkTextMessagePayload(input));
255
+ }
256
+
257
+ export async function sendCardMessage(input: SendCardMessageInput): Promise<SendTextMessageResult> {
258
+ return createLarkMessage(input, buildLarkMentionCardPayload(input));
259
+ }
260
+
135
261
  export interface AddMessageReactionInput {
136
262
  client: Lark.Client;
137
263
  messageId: string;