@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.
- package/README.md +66 -24
- package/package.json +16 -3
- package/src/agent-avatar.ts +30 -0
- package/src/agent-key.ts +28 -0
- package/src/agent-permissions.ts +359 -0
- package/src/agent-runtime.ts +810 -28
- package/src/agent-workspace.ts +183 -0
- package/src/app.ts +2183 -79
- package/src/args.ts +54 -7
- package/src/cli.ts +873 -14
- package/src/client.ts +482 -12
- package/src/coco.ts +9 -40
- package/src/codex.ts +33 -5
- package/src/config.ts +28 -4
- package/src/console.ts +460 -26
- package/src/daemon-client.ts +116 -3
- package/src/daemon.ts +958 -101
- package/src/db.ts +3216 -113
- package/src/delivery-ws.ts +269 -0
- package/src/format.ts +4 -1
- package/src/lark/app-registration.ts +141 -0
- package/src/lark/cli.ts +7 -137
- package/src/lark/credentials.ts +36 -3
- package/src/lark/event-router.ts +61 -5
- package/src/lark/inbound-events.ts +156 -3
- package/src/lark/server-integration.ts +659 -111
- package/src/lark/setup.ts +74 -5
- package/src/lark/ws-daemon.ts +136 -10
- package/src/local-api.ts +611 -14
- package/src/local-auth.ts +36 -3
- package/src/message-attachments.ts +71 -0
- package/src/messaging-cli.ts +741 -0
- package/src/messaging-status.ts +669 -0
- package/src/migrations/023_projects.ts +65 -0
- package/src/migrations/024_agents_model.ts +10 -0
- package/src/migrations/025_room_archive.ts +44 -0
- package/src/migrations/026_project_archive.ts +44 -0
- package/src/migrations/027_agent_permission_profiles.ts +16 -0
- package/src/migrations/028_lark_websocket_restart_state.ts +16 -0
- package/src/migrations/029_held_message_drafts.ts +32 -0
- package/src/migrations/030_agent_room_read_state.ts +25 -0
- package/src/migrations/031_room_tasks.ts +29 -0
- package/src/migrations/032_room_reminders.ts +29 -0
- package/src/migrations/033_room_saved_messages.ts +25 -0
- package/src/migrations/034_agent_activity_events.ts +27 -0
- package/src/migrations/035_agent_avatars.ts +17 -0
- package/src/migrations/036_project_agent_defaults.ts +21 -0
- package/src/migrations/037_message_attachments.ts +36 -0
- package/src/migrations/038_agent_activity_room_scope.ts +64 -0
- package/src/migrations/039_message_attachments_path.ts +34 -0
- package/src/migrations/040_message_attachments_file_schema.ts +80 -0
- package/src/migrations/041_room_system_events.ts +30 -0
- package/src/migrations/042_message_attachment_file_kind.ts +52 -0
- package/src/migrations/043_room_mode_skill_registry.ts +92 -0
- package/src/migrations/044_workflow_runtime.ts +69 -0
- package/src/migrations/045_skill_repository_ownership.ts +64 -0
- package/src/migrations.ts +70 -1
- package/src/neeko.ts +40 -4
- package/src/runtime-env.ts +179 -0
- package/src/runtime-registry.ts +83 -13
- package/src/server.ts +244 -4
- package/src/token-file.ts +13 -6
- package/src/types.ts +394 -0
- package/src/workflow-runtime.ts +275 -0
- package/src/web.ts +0 -904
package/src/lark/event-router.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { palIdentityHandle } from '../provider-identity.js';
|
|
|
14
14
|
* | message.chat_id | chatName (prefixed `lark:`) |
|
|
15
15
|
* | message.message_id | idempotencyKey |
|
|
16
16
|
* | sender.sender_id.open_id | sender |
|
|
17
|
-
* | message.mentions[
|
|
17
|
+
* | message.mentions[].id.open_id | mentions for group delivery; recipient for non-group direct routing |
|
|
18
18
|
* | message.content (parsed JSON text)| content |
|
|
19
19
|
* | message.root_id (thread reply) | parentId via lookup |
|
|
20
20
|
*
|
|
@@ -61,10 +61,16 @@ export interface MapLarkMessageResult {
|
|
|
61
61
|
status: 'ok' | 'skipped';
|
|
62
62
|
reason?: 'missing_message_id' | 'missing_chat_id' | 'missing_sender' | 'unsupported_message_type' | 'empty_text';
|
|
63
63
|
input?: CreateMessageInput;
|
|
64
|
-
/** Lark root_id when
|
|
64
|
+
/** Lark root_id, or parent_id when root_id is absent, used to pre-resolve the local topic parent. */
|
|
65
65
|
rootMessageId?: string;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export interface LarkImageResource {
|
|
69
|
+
fileKey: string;
|
|
70
|
+
filename: string;
|
|
71
|
+
mimeType: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
export function buildLockChatName(appId: string, chatId: string, chatType?: string): string {
|
|
69
75
|
if (chatType === 'group') {
|
|
70
76
|
return `lark:group:${chatId}`;
|
|
@@ -96,7 +102,7 @@ export function mentionsAllAgents(envelope: LarkMessageEnvelope): boolean {
|
|
|
96
102
|
if (isAllMention(mention)) return true;
|
|
97
103
|
}
|
|
98
104
|
const raw = parseLarkTextContent(envelope.message?.content, envelope.message?.message_type).toLowerCase();
|
|
99
|
-
return /(^|\s)@(all|所有人)(\s|$)/u.test(raw);
|
|
105
|
+
return /(^|\s)@(_all|all|所有人)(\s|$)/u.test(raw);
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
function normalizeMappedMentions(values: Array<string | null>): string[] {
|
|
@@ -125,6 +131,49 @@ function parseLarkTextContent(rawContent: string | undefined, messageType: strin
|
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
function parseJsonObject(rawContent: string | undefined): Record<string, unknown> | null {
|
|
135
|
+
if (!rawContent) return null;
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(rawContent) as unknown;
|
|
138
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function extractLarkImageResources(envelope: LarkMessageEnvelope): LarkImageResource[] {
|
|
145
|
+
const msg = envelope.message;
|
|
146
|
+
const out: LarkImageResource[] = [];
|
|
147
|
+
const seen = new Set<string>();
|
|
148
|
+
const add = (fileKey: unknown, filename?: unknown) => {
|
|
149
|
+
if (typeof fileKey !== 'string' || !fileKey.trim() || seen.has(fileKey)) return;
|
|
150
|
+
seen.add(fileKey);
|
|
151
|
+
out.push({
|
|
152
|
+
fileKey,
|
|
153
|
+
filename: typeof filename === 'string' && filename.trim() ? filename.trim() : `${fileKey}.jpg`,
|
|
154
|
+
mimeType: 'image/jpeg',
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
const parsed = parseJsonObject(msg?.content);
|
|
158
|
+
if (msg?.message_type === 'image') {
|
|
159
|
+
add(parsed?.image_key ?? parsed?.file_key, parsed?.file_name ?? parsed?.name);
|
|
160
|
+
}
|
|
161
|
+
if (msg?.message_type === 'post' && parsed) {
|
|
162
|
+
const content = parsed.content;
|
|
163
|
+
if (Array.isArray(content)) {
|
|
164
|
+
for (const line of content) {
|
|
165
|
+
if (!Array.isArray(line)) continue;
|
|
166
|
+
for (const item of line) {
|
|
167
|
+
if (!item || typeof item !== 'object') continue;
|
|
168
|
+
const record = item as Record<string, unknown>;
|
|
169
|
+
if (record.tag === 'img' || record.tag === 'image') add(record.image_key ?? record.file_key);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
128
177
|
/**
|
|
129
178
|
* Pure mapper: no DB access. Returns a `CreateMessageInput` skeleton (without
|
|
130
179
|
* parentId resolution — that happens in `resolveThreadParent`).
|
|
@@ -146,11 +195,14 @@ export function mapLarkMessageToCreateInput(input: MapLarkMessageInput): MapLark
|
|
|
146
195
|
...(mentionsAllAgents(envelope) ? [ALL_AGENTS_MENTION] : []),
|
|
147
196
|
...(msg.mentions?.map((m) => m.id?.open_id ? input.recipientByMentionOpenId?.get(m.id.open_id) ?? null : null) ?? []),
|
|
148
197
|
]);
|
|
198
|
+
const firstMappedMention = firstMention ? input.recipientByMentionOpenId?.get(firstMention) : undefined;
|
|
149
199
|
const recipient = input.recipientOverride !== undefined
|
|
150
200
|
? input.recipientOverride
|
|
151
|
-
:
|
|
201
|
+
: msg.chat_type === 'group'
|
|
202
|
+
? null
|
|
203
|
+
: firstMention ? firstMappedMention ?? firstMention : null;
|
|
152
204
|
const chatName = buildLockChatName(input.appId, msg.chat_id, msg.chat_type);
|
|
153
|
-
const rootMessageId = msg.root_id?.trim() || undefined;
|
|
205
|
+
const rootMessageId = msg.root_id?.trim() || msg.parent_id?.trim() || undefined;
|
|
154
206
|
|
|
155
207
|
const createInput: CreateMessageInput = {
|
|
156
208
|
chatName,
|
|
@@ -291,6 +343,8 @@ export function ingestLarkMessage(input: IngestLarkMessageInput): IngestLarkMess
|
|
|
291
343
|
content: mapped.input.content,
|
|
292
344
|
type: mapped.input.type,
|
|
293
345
|
idempotencyKey: mapped.input.idempotencyKey,
|
|
346
|
+
provider: 'lark',
|
|
347
|
+
mentions: mapped.input.mentions,
|
|
294
348
|
}
|
|
295
349
|
: threadOrphan
|
|
296
350
|
? {
|
|
@@ -300,6 +354,8 @@ export function ingestLarkMessage(input: IngestLarkMessageInput): IngestLarkMess
|
|
|
300
354
|
content: mapped.input.content,
|
|
301
355
|
type: mapped.input.type,
|
|
302
356
|
idempotencyKey: mapped.input.idempotencyKey,
|
|
357
|
+
provider: 'lark',
|
|
358
|
+
mentions: mapped.input.mentions,
|
|
303
359
|
}
|
|
304
360
|
: { ...mapped.input, sender: senderHandle || mapped.input.sender };
|
|
305
361
|
|
|
@@ -10,6 +10,8 @@ export interface InboundRawEvent {
|
|
|
10
10
|
raw_body_bytes: Uint8Array;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export type InboundRawEventSummary = Omit<InboundRawEvent, 'raw_body_bytes'>;
|
|
14
|
+
|
|
13
15
|
export interface StoreInboundEventInput {
|
|
14
16
|
appId: string;
|
|
15
17
|
rawBody: string | Uint8Array;
|
|
@@ -24,6 +26,31 @@ export interface StoreInboundEventResult {
|
|
|
24
26
|
duplicate: boolean;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
export interface RepairInboundEventParseFailuresOptions {
|
|
30
|
+
appId?: string;
|
|
31
|
+
limit?: number;
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RepairInboundEventParseFailuresResult {
|
|
36
|
+
dry_run: boolean;
|
|
37
|
+
scanned: number;
|
|
38
|
+
repaired: number;
|
|
39
|
+
unchanged: number;
|
|
40
|
+
conflicts: number;
|
|
41
|
+
errors: number;
|
|
42
|
+
rows: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
app_id: string;
|
|
45
|
+
old_event_id: string;
|
|
46
|
+
old_event_type: string;
|
|
47
|
+
new_event_id: string;
|
|
48
|
+
new_event_type: string;
|
|
49
|
+
status: 'repaired' | 'would_repair' | 'unchanged' | 'conflict' | 'error';
|
|
50
|
+
error?: string;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
const ENCODER = new TextEncoder();
|
|
28
55
|
|
|
29
56
|
function toBytes(body: string | Uint8Array): Uint8Array {
|
|
@@ -48,8 +75,9 @@ export async function parseEventEnvelope(rawBytes: Uint8Array): Promise<ParsedEn
|
|
|
48
75
|
const text = new TextDecoder('utf8', { fatal: false }).decode(rawBytes);
|
|
49
76
|
const json = JSON.parse(text) as Record<string, unknown>;
|
|
50
77
|
const header = (json.header && typeof json.header === 'object') ? json.header as Record<string, unknown> : null;
|
|
51
|
-
const
|
|
52
|
-
const
|
|
78
|
+
const event = (json.event && typeof json.event === 'object') ? json.event as Record<string, unknown> : null;
|
|
79
|
+
const eventId = (header?.event_id ?? event?.event_id ?? json.uuid) as unknown;
|
|
80
|
+
const eventType = (header?.event_type ?? event?.event_type ?? json.type) as unknown;
|
|
53
81
|
if (typeof eventId === 'string' && eventId.length > 0 && typeof eventType === 'string' && eventType.length > 0) {
|
|
54
82
|
return { event_id: eventId, event_type: eventType, parse_ok: 1 };
|
|
55
83
|
}
|
|
@@ -117,10 +145,22 @@ export function getInboundEvent(db: Database, id: string): InboundRawEvent | nul
|
|
|
117
145
|
|
|
118
146
|
export function listRecentInboundEvents(db: Database, limit = 20): InboundRawEvent[] {
|
|
119
147
|
return db
|
|
120
|
-
.query('SELECT id, received_at, app_id, event_type, event_id, parse_ok, raw_body_bytes FROM channel_inbound_raw_events ORDER BY received_at DESC,
|
|
148
|
+
.query('SELECT id, received_at, app_id, event_type, event_id, parse_ok, raw_body_bytes FROM channel_inbound_raw_events ORDER BY received_at DESC, rowid DESC LIMIT ?')
|
|
121
149
|
.all(Math.max(1, Math.min(limit, 500))) as InboundRawEvent[];
|
|
122
150
|
}
|
|
123
151
|
|
|
152
|
+
export function latestInboundEventSummary(db: Database, appId: string): InboundRawEventSummary | null {
|
|
153
|
+
return db
|
|
154
|
+
.query("SELECT id, received_at, app_id, event_type, event_id, parse_ok FROM channel_inbound_raw_events WHERE app_id = ? AND event_type NOT LIKE 'pal.probe.%' ORDER BY received_at DESC, rowid DESC LIMIT 1")
|
|
155
|
+
.get(appId) as InboundRawEventSummary | null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function latestProbeInboundEventSummary(db: Database, appId: string): InboundRawEventSummary | null {
|
|
159
|
+
return db
|
|
160
|
+
.query("SELECT id, received_at, app_id, event_type, event_id, parse_ok FROM channel_inbound_raw_events WHERE app_id = ? AND event_type LIKE 'pal.probe.%' ORDER BY received_at DESC, rowid DESC LIMIT 1")
|
|
161
|
+
.get(appId) as InboundRawEventSummary | null;
|
|
162
|
+
}
|
|
163
|
+
|
|
124
164
|
export function countInboundEvents(db: Database, appId?: string): number {
|
|
125
165
|
if (appId) {
|
|
126
166
|
const row = db.query('SELECT COUNT(*) AS n FROM channel_inbound_raw_events WHERE app_id = ?').get(appId) as { n: number };
|
|
@@ -129,3 +169,116 @@ export function countInboundEvents(db: Database, appId?: string): number {
|
|
|
129
169
|
const row = db.query('SELECT COUNT(*) AS n FROM channel_inbound_raw_events').get() as { n: number };
|
|
130
170
|
return row.n;
|
|
131
171
|
}
|
|
172
|
+
|
|
173
|
+
export function countProviderInboundEvents(db: Database, appId?: string): number {
|
|
174
|
+
if (appId) {
|
|
175
|
+
const row = db.query("SELECT COUNT(*) AS n FROM channel_inbound_raw_events WHERE app_id = ? AND event_type NOT LIKE 'pal.probe.%'").get(appId) as { n: number };
|
|
176
|
+
return row.n;
|
|
177
|
+
}
|
|
178
|
+
const row = db.query("SELECT COUNT(*) AS n FROM channel_inbound_raw_events WHERE event_type NOT LIKE 'pal.probe.%'").get() as { n: number };
|
|
179
|
+
return row.n;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function countProbeInboundEvents(db: Database, appId?: string): number {
|
|
183
|
+
if (appId) {
|
|
184
|
+
const row = db.query("SELECT COUNT(*) AS n FROM channel_inbound_raw_events WHERE app_id = ? AND event_type LIKE 'pal.probe.%'").get(appId) as { n: number };
|
|
185
|
+
return row.n;
|
|
186
|
+
}
|
|
187
|
+
const row = db.query("SELECT COUNT(*) AS n FROM channel_inbound_raw_events WHERE event_type LIKE 'pal.probe.%'").get() as { n: number };
|
|
188
|
+
return row.n;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function countInboundParseFailures(db: Database, appId?: string): number {
|
|
192
|
+
if (appId) {
|
|
193
|
+
const row = db.query('SELECT COUNT(*) AS n FROM channel_inbound_raw_events WHERE app_id = ? AND parse_ok = 0').get(appId) as { n: number };
|
|
194
|
+
return row.n;
|
|
195
|
+
}
|
|
196
|
+
const row = db.query('SELECT COUNT(*) AS n FROM channel_inbound_raw_events WHERE parse_ok = 0').get() as { n: number };
|
|
197
|
+
return row.n;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function repairInboundEventParseFailures(
|
|
201
|
+
db: Database,
|
|
202
|
+
options: RepairInboundEventParseFailuresOptions = {},
|
|
203
|
+
): Promise<RepairInboundEventParseFailuresResult> {
|
|
204
|
+
const limit = Math.max(1, Math.min(options.limit ?? 100, 1000));
|
|
205
|
+
const dryRun = options.dryRun ?? false;
|
|
206
|
+
const rows = options.appId
|
|
207
|
+
? db
|
|
208
|
+
.query('SELECT id, app_id, event_type, event_id, raw_body_bytes FROM channel_inbound_raw_events WHERE app_id = ? AND parse_ok = 0 ORDER BY received_at DESC, rowid DESC LIMIT ?')
|
|
209
|
+
.all(options.appId, limit)
|
|
210
|
+
: db
|
|
211
|
+
.query('SELECT id, app_id, event_type, event_id, raw_body_bytes FROM channel_inbound_raw_events WHERE parse_ok = 0 ORDER BY received_at DESC, rowid DESC LIMIT ?')
|
|
212
|
+
.all(limit);
|
|
213
|
+
const result: RepairInboundEventParseFailuresResult = {
|
|
214
|
+
dry_run: dryRun,
|
|
215
|
+
scanned: rows.length,
|
|
216
|
+
repaired: 0,
|
|
217
|
+
unchanged: 0,
|
|
218
|
+
conflicts: 0,
|
|
219
|
+
errors: 0,
|
|
220
|
+
rows: [],
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
for (const row of rows as Array<{ id: string; app_id: string; event_type: string; event_id: string; raw_body_bytes: Uint8Array }>) {
|
|
224
|
+
const parsed = await parseEventEnvelope(row.raw_body_bytes);
|
|
225
|
+
if (parsed.parse_ok !== 1) {
|
|
226
|
+
result.unchanged += 1;
|
|
227
|
+
result.rows.push({
|
|
228
|
+
id: row.id,
|
|
229
|
+
app_id: row.app_id,
|
|
230
|
+
old_event_id: row.event_id,
|
|
231
|
+
old_event_type: row.event_type,
|
|
232
|
+
new_event_id: parsed.event_id,
|
|
233
|
+
new_event_type: parsed.event_type,
|
|
234
|
+
status: 'unchanged',
|
|
235
|
+
});
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (dryRun) {
|
|
239
|
+
result.repaired += 1;
|
|
240
|
+
result.rows.push({
|
|
241
|
+
id: row.id,
|
|
242
|
+
app_id: row.app_id,
|
|
243
|
+
old_event_id: row.event_id,
|
|
244
|
+
old_event_type: row.event_type,
|
|
245
|
+
new_event_id: parsed.event_id,
|
|
246
|
+
new_event_type: parsed.event_type,
|
|
247
|
+
status: 'would_repair',
|
|
248
|
+
});
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
db
|
|
253
|
+
.query('UPDATE channel_inbound_raw_events SET event_id = ?, event_type = ?, parse_ok = 1 WHERE id = ?')
|
|
254
|
+
.run(parsed.event_id, parsed.event_type, row.id);
|
|
255
|
+
result.repaired += 1;
|
|
256
|
+
result.rows.push({
|
|
257
|
+
id: row.id,
|
|
258
|
+
app_id: row.app_id,
|
|
259
|
+
old_event_id: row.event_id,
|
|
260
|
+
old_event_type: row.event_type,
|
|
261
|
+
new_event_id: parsed.event_id,
|
|
262
|
+
new_event_type: parsed.event_type,
|
|
263
|
+
status: 'repaired',
|
|
264
|
+
});
|
|
265
|
+
} catch (err) {
|
|
266
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
267
|
+
const conflict = message.toLowerCase().includes('unique') || message.toLowerCase().includes('constraint');
|
|
268
|
+
if (conflict) result.conflicts += 1;
|
|
269
|
+
else result.errors += 1;
|
|
270
|
+
result.rows.push({
|
|
271
|
+
id: row.id,
|
|
272
|
+
app_id: row.app_id,
|
|
273
|
+
old_event_id: row.event_id,
|
|
274
|
+
old_event_type: row.event_type,
|
|
275
|
+
new_event_id: parsed.event_id,
|
|
276
|
+
new_event_type: parsed.event_type,
|
|
277
|
+
status: conflict ? 'conflict' : 'error',
|
|
278
|
+
error: message,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return result;
|
|
284
|
+
}
|