@qearlyao/familiar 0.2.3 → 0.2.5
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 +5 -2
- package/config.example.toml +1 -1
- package/dist/added-models.js +6 -15
- package/dist/agent-events.js +1 -3
- package/dist/agent.js +3 -4
- package/dist/browser-tools.js +84 -30
- package/dist/chat-log.js +3 -2
- package/dist/cli.js +2 -2
- package/dist/config-overrides.js +5 -14
- package/dist/config-registry.js +1 -4
- package/dist/config.js +45 -113
- package/dist/contact-note.js +2 -12
- package/dist/data-retention.js +1 -3
- package/dist/discord.js +2 -2
- package/dist/generated-media.js +3 -2
- package/dist/hot-reload.js +1 -3
- package/dist/image-gen.js +102 -61
- package/dist/inbound-attachments.js +53 -22
- package/dist/memory/diary/ambient-injector.js +1 -3
- package/dist/memory/diary/ambient.js +1 -3
- package/dist/memory/diary/chunks.js +1 -3
- package/dist/memory/diary/indexer.js +1 -3
- package/dist/memory/doctor.js +3 -8
- package/dist/memory/index/chunk-indexer.js +6 -2
- package/dist/memory/index/retrieval.js +1 -3
- package/dist/memory/index/store.js +47 -19
- package/dist/memory/lcm/backfill.js +19 -16
- package/dist/memory/lcm/context-transformer.js +12 -24
- package/dist/memory/lcm/context.js +10 -4
- package/dist/memory/lcm/eviction-score.js +25 -13
- package/dist/memory/lcm/indexer.js +1 -5
- package/dist/memory/lcm/normalize.js +22 -1
- package/dist/memory/lcm/store.js +27 -24
- package/dist/memory/operator.js +2 -4
- package/dist/memory/service.js +1 -3
- package/dist/memory/tools.js +0 -4
- package/dist/memory/util.js +6 -0
- package/dist/models.js +3 -0
- package/dist/persona.js +2 -14
- package/dist/runtime.js +2 -23
- package/dist/scheduler.js +15 -49
- package/dist/service.js +24 -14
- package/dist/settings.js +7 -32
- package/dist/tts.js +0 -6
- package/dist/util/fs.js +41 -0
- package/dist/util/guards.js +8 -0
- package/dist/util/image-mime.js +31 -0
- package/dist/util/time.js +29 -0
- package/dist/web-auth.js +4 -1
- package/dist/web-tools.js +8 -5
- package/dist/web.js +188 -62
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/web/dist/assets/index-B23WT77N.js +63 -0
- package/web/dist/assets/index-D3MotFzN.css +2 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-C-w9fjBf.js +0 -61
- package/web/dist/assets/index-CcQ13VAY.css +0 -2
package/dist/web-tools.js
CHANGED
|
@@ -69,7 +69,7 @@ class PageCache {
|
|
|
69
69
|
this.entries.delete(url);
|
|
70
70
|
return undefined;
|
|
71
71
|
}
|
|
72
|
-
entry.
|
|
72
|
+
entry.lastAccessed = Date.now();
|
|
73
73
|
this.entries.delete(url);
|
|
74
74
|
this.entries.set(url, entry);
|
|
75
75
|
return entry;
|
|
@@ -79,10 +79,12 @@ class PageCache {
|
|
|
79
79
|
return;
|
|
80
80
|
if (this.entries.has(url))
|
|
81
81
|
this.entries.delete(url);
|
|
82
|
+
const now = Date.now();
|
|
82
83
|
this.entries.set(url, {
|
|
83
84
|
content,
|
|
84
85
|
provider,
|
|
85
|
-
fetchedAt:
|
|
86
|
+
fetchedAt: now,
|
|
87
|
+
lastAccessed: now,
|
|
86
88
|
});
|
|
87
89
|
while (this.entries.size > this.capacity) {
|
|
88
90
|
const oldest = this.entries.keys().next().value;
|
|
@@ -382,7 +384,7 @@ function formatFetchContent(url, provider, chunk) {
|
|
|
382
384
|
chunk.text,
|
|
383
385
|
];
|
|
384
386
|
if (chunk.hasMore && chunk.nextOffset !== undefined) {
|
|
385
|
-
lines.push("", `[More content available. Next chunk:
|
|
387
|
+
lines.push("", `[More content available. Next chunk: fetch_web(url="${url}", offset=${chunk.nextOffset})]`);
|
|
386
388
|
}
|
|
387
389
|
return prefixUntrustedWebContent(lines.join("\n"));
|
|
388
390
|
}
|
|
@@ -778,7 +780,7 @@ function makeSearchTool(config) {
|
|
|
778
780
|
if (config.apiKeys.EXA_API_KEY)
|
|
779
781
|
providers.exa = createExaProvider(config.apiKeys.EXA_API_KEY);
|
|
780
782
|
return {
|
|
781
|
-
name: "
|
|
783
|
+
name: "search_web",
|
|
782
784
|
label: "Web Search",
|
|
783
785
|
description: "look something up on the open web. returns titles, urls, snippets, and dates when present. depth=thorough swaps brevity for inline excerpts.",
|
|
784
786
|
parameters: webSearchSchema,
|
|
@@ -847,7 +849,7 @@ function makeSearchTool(config) {
|
|
|
847
849
|
function makeFetchTool(config) {
|
|
848
850
|
const providers = createFetchProviders(config);
|
|
849
851
|
return {
|
|
850
|
-
name: "
|
|
852
|
+
name: "fetch_web",
|
|
851
853
|
label: "Web Fetch",
|
|
852
854
|
description: "pull a webpage down as clean markdown.",
|
|
853
855
|
parameters: webFetchSchema,
|
|
@@ -923,6 +925,7 @@ export function createWebTools(_config) {
|
|
|
923
925
|
}
|
|
924
926
|
export const __webToolsTest = {
|
|
925
927
|
PageCache,
|
|
928
|
+
createWebTools,
|
|
926
929
|
createTestSearchProvider,
|
|
927
930
|
createFetchProviders,
|
|
928
931
|
formatFetchContent,
|
package/dist/web.js
CHANGED
|
@@ -148,6 +148,8 @@ function webAttachments(config, attachments) {
|
|
|
148
148
|
}));
|
|
149
149
|
}
|
|
150
150
|
function attachmentDerivedText(attachment) {
|
|
151
|
+
if (attachment.derived?.text?.label === "preview")
|
|
152
|
+
return undefined;
|
|
151
153
|
return attachment.derived?.text?.text;
|
|
152
154
|
}
|
|
153
155
|
function toolError(result) {
|
|
@@ -216,23 +218,79 @@ function mergeToolEvent(existing, patch) {
|
|
|
216
218
|
startedAt: existing?.startedAt ?? patch.startedAt,
|
|
217
219
|
};
|
|
218
220
|
}
|
|
221
|
+
function stepId(messageId, kind, index) {
|
|
222
|
+
return `${messageId}-${kind}-${index}`;
|
|
223
|
+
}
|
|
224
|
+
function closeOpenContentSteps(steps, now) {
|
|
225
|
+
for (const step of steps) {
|
|
226
|
+
if (step.kind === "thinking" && !step.complete) {
|
|
227
|
+
step.complete = true;
|
|
228
|
+
step.endedAt ??= now;
|
|
229
|
+
}
|
|
230
|
+
if (step.kind === "text" && !step.complete)
|
|
231
|
+
step.complete = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function appendDeltaStep(steps, messageId, part, content, now) {
|
|
235
|
+
const last = steps.at(-1);
|
|
236
|
+
if (part === "thinking") {
|
|
237
|
+
if (last?.kind === "thinking" && !last.complete) {
|
|
238
|
+
last.text += content;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
closeOpenContentSteps(steps, now);
|
|
242
|
+
steps.push({
|
|
243
|
+
kind: "thinking",
|
|
244
|
+
id: stepId(messageId, "thinking", steps.length),
|
|
245
|
+
text: content,
|
|
246
|
+
startedAt: now,
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (last?.kind === "text" && !last.complete) {
|
|
251
|
+
last.text += content;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
closeOpenContentSteps(steps, now);
|
|
255
|
+
steps.push({ kind: "text", id: stepId(messageId, "text", steps.length), text: content });
|
|
256
|
+
}
|
|
257
|
+
function upsertToolStep(steps, tool, now) {
|
|
258
|
+
const index = steps.findIndex((step) => step.kind === "tool" && step.tool.id === tool.id);
|
|
259
|
+
if (index >= 0) {
|
|
260
|
+
const existing = steps[index];
|
|
261
|
+
if (existing?.kind === "tool")
|
|
262
|
+
existing.tool = mergeToolEvent(existing.tool, tool);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
closeOpenContentSteps(steps, now);
|
|
266
|
+
steps.push({ kind: "tool", id: tool.id, tool });
|
|
267
|
+
}
|
|
219
268
|
function applyStoredAgentEventToMessage(message, record, options) {
|
|
220
269
|
const event = record.event;
|
|
221
270
|
const ts = toUnixMs(record.ts);
|
|
271
|
+
message.steps ??= [];
|
|
272
|
+
const steps = message.steps;
|
|
222
273
|
if (event.type === "message_update") {
|
|
223
274
|
const assistantEvent = event.assistantMessageEvent;
|
|
224
275
|
if (assistantEvent.type === "text_delta") {
|
|
276
|
+
appendDeltaStep(steps, message.id, "text", assistantEvent.delta, ts);
|
|
225
277
|
if (options.applyTextDeltas)
|
|
226
278
|
message.text += assistantEvent.delta;
|
|
227
279
|
}
|
|
228
|
-
if (assistantEvent.type === "thinking_delta"
|
|
229
|
-
|
|
280
|
+
if (assistantEvent.type === "thinking_delta") {
|
|
281
|
+
appendDeltaStep(steps, message.id, "thinking", assistantEvent.delta, ts);
|
|
282
|
+
if (options.applyThinkingDeltas)
|
|
283
|
+
message.thinking = `${message.thinking ?? ""}${assistantEvent.delta}`;
|
|
230
284
|
}
|
|
231
285
|
}
|
|
232
|
-
if (event.type === "message_end"
|
|
233
|
-
|
|
286
|
+
if (event.type === "message_end") {
|
|
287
|
+
closeOpenContentSteps(steps, ts);
|
|
288
|
+
if (event.usage)
|
|
289
|
+
message.usage = event.usage;
|
|
290
|
+
}
|
|
234
291
|
const tool = toolFromStoredAgentEvent(event, ts);
|
|
235
292
|
if (tool) {
|
|
293
|
+
upsertToolStep(steps, tool, ts);
|
|
236
294
|
const tools = message.tools ?? [];
|
|
237
295
|
const index = tools.findIndex((candidate) => candidate.id === tool.id);
|
|
238
296
|
if (index >= 0) {
|
|
@@ -244,6 +302,29 @@ function applyStoredAgentEventToMessage(message, record, options) {
|
|
|
244
302
|
message.tools = tools;
|
|
245
303
|
}
|
|
246
304
|
}
|
|
305
|
+
function ensureFallbackSteps(message) {
|
|
306
|
+
if (message.steps?.length)
|
|
307
|
+
return;
|
|
308
|
+
const steps = [];
|
|
309
|
+
if (message.thinking || message.thinkingMs != null) {
|
|
310
|
+
const endedAt = message.ts;
|
|
311
|
+
steps.push({
|
|
312
|
+
kind: "thinking",
|
|
313
|
+
id: stepId(message.id, "thinking", steps.length),
|
|
314
|
+
text: message.thinking ?? "",
|
|
315
|
+
startedAt: endedAt - (message.thinkingMs ?? 0),
|
|
316
|
+
endedAt,
|
|
317
|
+
complete: true,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
for (const tool of message.tools ?? [])
|
|
321
|
+
steps.push({ kind: "tool", id: tool.id, tool });
|
|
322
|
+
if (message.text) {
|
|
323
|
+
steps.push({ kind: "text", id: stepId(message.id, "text", steps.length), text: message.text, complete: true });
|
|
324
|
+
}
|
|
325
|
+
if (steps.length)
|
|
326
|
+
message.steps = steps;
|
|
327
|
+
}
|
|
247
328
|
function webMessagesFromRecords(config, records, assistantName) {
|
|
248
329
|
const messages = [];
|
|
249
330
|
const messagesById = new Map();
|
|
@@ -256,7 +337,7 @@ function webMessagesFromRecords(config, records, assistantName) {
|
|
|
256
337
|
const pending = pendingAgentEvents.get(message.id) ?? [];
|
|
257
338
|
for (const pendingRecord of pending) {
|
|
258
339
|
applyStoredAgentEventToMessage(message, pendingRecord, {
|
|
259
|
-
applyTextDeltas: !message.text,
|
|
340
|
+
applyTextDeltas: !message.text && !message.silent,
|
|
260
341
|
applyThinkingDeltas: !message.thinking,
|
|
261
342
|
});
|
|
262
343
|
}
|
|
@@ -266,7 +347,7 @@ function webMessagesFromRecords(config, records, assistantName) {
|
|
|
266
347
|
const existing = messagesById.get(record.messageId);
|
|
267
348
|
if (existing) {
|
|
268
349
|
applyStoredAgentEventToMessage(existing, record, {
|
|
269
|
-
applyTextDeltas:
|
|
350
|
+
applyTextDeltas: !existing.silent,
|
|
270
351
|
applyThinkingDeltas: true,
|
|
271
352
|
});
|
|
272
353
|
}
|
|
@@ -277,8 +358,17 @@ function webMessagesFromRecords(config, records, assistantName) {
|
|
|
277
358
|
}
|
|
278
359
|
}
|
|
279
360
|
}
|
|
361
|
+
for (const message of messages)
|
|
362
|
+
ensureFallbackSteps(message);
|
|
280
363
|
return messages;
|
|
281
364
|
}
|
|
365
|
+
function webHistoryPayload(config, records, assistantName, channelKey, options) {
|
|
366
|
+
const messages = webMessagesFromRecords(config, records, assistantName);
|
|
367
|
+
const end = options.before ? messages.findIndex((message) => message.id === options.before) : messages.length;
|
|
368
|
+
const safeEnd = end >= 0 ? end : messages.length;
|
|
369
|
+
const page = messages.slice(Math.max(0, safeEnd - options.limit), safeEnd);
|
|
370
|
+
return { messages: page, hasMore: safeEnd - options.limit > 0, channelKey };
|
|
371
|
+
}
|
|
282
372
|
function webMessageFromRecord(config, record, assistantName) {
|
|
283
373
|
if (!isUserVisibleRuntimeRecord(record))
|
|
284
374
|
return undefined;
|
|
@@ -365,9 +455,32 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
365
455
|
const clients = new Set();
|
|
366
456
|
const eventsByChannel = new Map();
|
|
367
457
|
const runtimeSubscriptions = new Map();
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
const
|
|
458
|
+
const IN_FLIGHT_TTL_MS = 10 * 60 * 1000;
|
|
459
|
+
const inFlightMessages = new Map();
|
|
460
|
+
const getOrCreateInFlight = (messageIdValue) => {
|
|
461
|
+
let entry = inFlightMessages.get(messageIdValue);
|
|
462
|
+
if (!entry) {
|
|
463
|
+
entry = { locallyStreamed: false, startedSilent: false, lastActiveAt: Date.now() };
|
|
464
|
+
inFlightMessages.set(messageIdValue, entry);
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
entry.lastActiveAt = Date.now();
|
|
468
|
+
}
|
|
469
|
+
return entry;
|
|
470
|
+
};
|
|
471
|
+
const touchInFlight = (messageIdValue) => {
|
|
472
|
+
const entry = inFlightMessages.get(messageIdValue);
|
|
473
|
+
if (entry)
|
|
474
|
+
entry.lastActiveAt = Date.now();
|
|
475
|
+
};
|
|
476
|
+
const inFlightGcTimer = setInterval(() => {
|
|
477
|
+
const cutoff = Date.now() - IN_FLIGHT_TTL_MS;
|
|
478
|
+
for (const [id, entry] of inFlightMessages) {
|
|
479
|
+
if (entry.lastActiveAt < cutoff)
|
|
480
|
+
inFlightMessages.delete(id);
|
|
481
|
+
}
|
|
482
|
+
}, 60 * 1000);
|
|
483
|
+
inFlightGcTimer.unref?.();
|
|
371
484
|
const publish = (event) => {
|
|
372
485
|
const fullEvent = { ...event, eventId: eventId(), ts: event.ts ?? Date.now() };
|
|
373
486
|
const events = eventsByChannel.get(fullEvent.channelKey ?? "") ?? [];
|
|
@@ -392,16 +505,21 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
392
505
|
};
|
|
393
506
|
const publishDelta = (channelKey, messageIdValue, part, text, ts) => publish({ type: "delta", channelKey, messageId: messageIdValue, part, content: text, text, ts });
|
|
394
507
|
const publishStoredAgentEvent = (channelKey, messageIdValue, storedEvent, ts) => {
|
|
508
|
+
touchInFlight(messageIdValue);
|
|
395
509
|
if (storedEvent.type === "message_start" && storedEvent.role === "assistant") {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
510
|
+
const entry = getOrCreateInFlight(messageIdValue);
|
|
511
|
+
entry.locallyStreamed = true;
|
|
512
|
+
entry.silentFilter = createSilentFilterState();
|
|
513
|
+
entry.pendingStartTs = ts;
|
|
514
|
+
entry.startedSilent = false;
|
|
399
515
|
}
|
|
400
516
|
const startedSilentMessage = () => {
|
|
401
|
-
|
|
517
|
+
const entry = inFlightMessages.get(messageIdValue);
|
|
518
|
+
if (!entry || entry.startedSilent)
|
|
402
519
|
return false;
|
|
403
|
-
const startTs =
|
|
404
|
-
|
|
520
|
+
const startTs = entry.pendingStartTs;
|
|
521
|
+
entry.pendingStartTs = undefined;
|
|
522
|
+
entry.startedSilent = true;
|
|
405
523
|
publish({
|
|
406
524
|
type: "message_started",
|
|
407
525
|
channelKey,
|
|
@@ -419,7 +537,7 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
419
537
|
publishDelta(channelKey, messageIdValue, "thinking", assistantEvent.delta, ts);
|
|
420
538
|
}
|
|
421
539
|
if (assistantEvent.type === "text_delta") {
|
|
422
|
-
const filter =
|
|
540
|
+
const filter = inFlightMessages.get(messageIdValue)?.silentFilter;
|
|
423
541
|
if (!filter) {
|
|
424
542
|
startedSilentMessage();
|
|
425
543
|
publishDelta(channelKey, messageIdValue, "text", assistantEvent.delta, ts);
|
|
@@ -437,9 +555,10 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
437
555
|
startedSilentMessage();
|
|
438
556
|
}
|
|
439
557
|
if (storedEvent.type === "message_end" && storedEvent.role === "assistant") {
|
|
440
|
-
const
|
|
558
|
+
const entry = inFlightMessages.get(messageIdValue);
|
|
559
|
+
const filter = entry?.silentFilter;
|
|
441
560
|
let silent = false;
|
|
442
|
-
if (filter) {
|
|
561
|
+
if (filter && entry) {
|
|
443
562
|
const final = finalizeSilentFilter(filter);
|
|
444
563
|
silent = final.silent;
|
|
445
564
|
if (!silent) {
|
|
@@ -449,9 +568,10 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
449
568
|
}
|
|
450
569
|
}
|
|
451
570
|
else {
|
|
452
|
-
|
|
571
|
+
entry.startedSilent = true;
|
|
572
|
+
entry.pendingStartTs = undefined;
|
|
453
573
|
}
|
|
454
|
-
|
|
574
|
+
entry.silentFilter = undefined;
|
|
455
575
|
}
|
|
456
576
|
else {
|
|
457
577
|
startedSilentMessage();
|
|
@@ -487,6 +607,7 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
487
607
|
type: "message_completed",
|
|
488
608
|
channelKey: runtime.channelKey,
|
|
489
609
|
messageId: record.messageId,
|
|
610
|
+
attachments: webAttachments(config, record.attachments),
|
|
490
611
|
ts: toUnixMs(record.ts),
|
|
491
612
|
});
|
|
492
613
|
}
|
|
@@ -501,7 +622,8 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
501
622
|
silent: record.silent || undefined,
|
|
502
623
|
ts: toUnixMs(record.ts),
|
|
503
624
|
};
|
|
504
|
-
if (
|
|
625
|
+
if (inFlightMessages.get(outboundId)?.locallyStreamed) {
|
|
626
|
+
inFlightMessages.delete(outboundId);
|
|
505
627
|
publish(completion);
|
|
506
628
|
return;
|
|
507
629
|
}
|
|
@@ -638,7 +760,8 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
638
760
|
attachments: webAttachments(config, reply.attachments),
|
|
639
761
|
silent: parsed.silent || undefined,
|
|
640
762
|
});
|
|
641
|
-
|
|
763
|
+
const entry = getOrCreateInFlight(assistantMessageId);
|
|
764
|
+
entry.locallyStreamed = true;
|
|
642
765
|
return {
|
|
643
766
|
text: finalText,
|
|
644
767
|
messageId: assistantMessageId,
|
|
@@ -744,12 +867,8 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
744
867
|
if (request.method === "GET" && url.pathname === "/api/web/history") {
|
|
745
868
|
const runtime = await getRuntime(getChannelKeyFromRequest(url));
|
|
746
869
|
const limit = Math.min(Math.max(Number(url.searchParams.get("limit") ?? 50) || 50, 1), 200);
|
|
747
|
-
const before = url.searchParams.get("before");
|
|
748
|
-
|
|
749
|
-
const end = before ? messages.findIndex((message) => message.id === before) : messages.length;
|
|
750
|
-
const safeEnd = end >= 0 ? end : messages.length;
|
|
751
|
-
const page = messages.slice(Math.max(0, safeEnd - limit), safeEnd);
|
|
752
|
-
sendJson(response, 200, { messages: page, hasMore: safeEnd - limit > 0, channelKey: runtime.channelKey });
|
|
870
|
+
const before = url.searchParams.get("before") ?? undefined;
|
|
871
|
+
sendJson(response, 200, webHistoryPayload(config, runtime.getRecords(), personaName, runtime.channelKey, { limit, before }));
|
|
753
872
|
return true;
|
|
754
873
|
}
|
|
755
874
|
if (request.method === "GET" && url.pathname === "/api/web/agent/settings") {
|
|
@@ -1010,49 +1129,53 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
1010
1129
|
netSocket.destroy();
|
|
1011
1130
|
return;
|
|
1012
1131
|
}
|
|
1013
|
-
if (!acceptWebSocket(request, netSocket))
|
|
1014
|
-
return;
|
|
1015
|
-
netSocket.setNoDelay(true);
|
|
1016
1132
|
const requestedChannelKey = url.searchParams.get("channelKey") || undefined;
|
|
1017
|
-
const client = { socket: netSocket, channelKey: requestedChannelKey, authed: false };
|
|
1018
|
-
clients.add(client);
|
|
1019
|
-
let frameBuffer = Buffer.alloc(0);
|
|
1020
1133
|
void getRuntime(requestedChannelKey)
|
|
1021
1134
|
.then((runtime) => {
|
|
1022
|
-
|
|
1135
|
+
if (netSocket.destroyed)
|
|
1136
|
+
return;
|
|
1137
|
+
if (!acceptWebSocket(request, netSocket))
|
|
1138
|
+
return;
|
|
1139
|
+
netSocket.setNoDelay(true);
|
|
1140
|
+
const client = { socket: netSocket, channelKey: runtime.channelKey, authed: false };
|
|
1141
|
+
clients.add(client);
|
|
1142
|
+
let frameBuffer = Buffer.alloc(0);
|
|
1143
|
+
netSocket.on("data", (chunk) => {
|
|
1144
|
+
try {
|
|
1145
|
+
frameBuffer = Buffer.concat([frameBuffer, chunk]);
|
|
1146
|
+
const decoded = decodeFrames(frameBuffer);
|
|
1147
|
+
frameBuffer = decoded.remaining;
|
|
1148
|
+
if (decoded.close)
|
|
1149
|
+
netSocket.destroy();
|
|
1150
|
+
for (const raw of decoded.messages) {
|
|
1151
|
+
const message = JSON.parse(raw);
|
|
1152
|
+
if (isObject(message) && message.type === "hello") {
|
|
1153
|
+
if (!client.channelKey)
|
|
1154
|
+
continue;
|
|
1155
|
+
replay(client, client.channelKey, typeof message.lastEventId === "string" ? message.lastEventId : null);
|
|
1156
|
+
}
|
|
1157
|
+
if (isObject(message) && message.type === "abort") {
|
|
1158
|
+
void getRuntime(client.channelKey).then(async (runtime) => {
|
|
1159
|
+
familiarAgent.requestSoftStop(runtime.channelKey);
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
catch (error) {
|
|
1165
|
+
console.error("WebSocket frame handling failed", error);
|
|
1166
|
+
netSocket.destroy();
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
netSocket.on("close", () => clients.delete(client));
|
|
1170
|
+
netSocket.on("error", () => clients.delete(client));
|
|
1023
1171
|
})
|
|
1024
1172
|
.catch((error) => {
|
|
1025
1173
|
console.error("WebSocket runtime lookup failed", error);
|
|
1026
|
-
netSocket.
|
|
1027
|
-
|
|
1028
|
-
netSocket.on("data", (chunk) => {
|
|
1029
|
-
try {
|
|
1030
|
-
frameBuffer = Buffer.concat([frameBuffer, chunk]);
|
|
1031
|
-
const decoded = decodeFrames(frameBuffer);
|
|
1032
|
-
frameBuffer = decoded.remaining;
|
|
1033
|
-
if (decoded.close)
|
|
1034
|
-
netSocket.destroy();
|
|
1035
|
-
for (const raw of decoded.messages) {
|
|
1036
|
-
const message = JSON.parse(raw);
|
|
1037
|
-
if (isObject(message) && message.type === "hello") {
|
|
1038
|
-
if (!client.channelKey)
|
|
1039
|
-
continue;
|
|
1040
|
-
replay(client, client.channelKey, typeof message.lastEventId === "string" ? message.lastEventId : null);
|
|
1041
|
-
}
|
|
1042
|
-
if (isObject(message) && message.type === "abort") {
|
|
1043
|
-
void getRuntime(client.channelKey).then(async (runtime) => {
|
|
1044
|
-
familiarAgent.requestSoftStop(runtime.channelKey);
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
catch (error) {
|
|
1050
|
-
console.error("WebSocket frame handling failed", error);
|
|
1174
|
+
if (!netSocket.destroyed) {
|
|
1175
|
+
netSocket.write("HTTP/1.1 503 Service Unavailable\r\n\r\n");
|
|
1051
1176
|
netSocket.destroy();
|
|
1052
1177
|
}
|
|
1053
1178
|
});
|
|
1054
|
-
netSocket.on("close", () => clients.delete(client));
|
|
1055
|
-
netSocket.on("error", () => clients.delete(client));
|
|
1056
1179
|
});
|
|
1057
1180
|
await new Promise((resolveListen, rejectListen) => {
|
|
1058
1181
|
server.once("error", rejectListen);
|
|
@@ -1065,6 +1188,7 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
1065
1188
|
return {
|
|
1066
1189
|
server,
|
|
1067
1190
|
async stop() {
|
|
1191
|
+
clearInterval(inFlightGcTimer);
|
|
1068
1192
|
for (const client of clients)
|
|
1069
1193
|
client.socket.destroy();
|
|
1070
1194
|
clients.clear();
|
|
@@ -1080,4 +1204,6 @@ export async function startWebDaemon(config, familiarAgent, discordDaemon, optio
|
|
|
1080
1204
|
export const __webTest = {
|
|
1081
1205
|
memeCatalogPath,
|
|
1082
1206
|
parseMemeCatalog,
|
|
1207
|
+
webHistoryPayload,
|
|
1208
|
+
webMessagesFromRecords,
|
|
1083
1209
|
};
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qearlyao/familiar",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@qearlyao/familiar",
|
|
9
|
-
"version": "0.2.
|
|
9
|
+
"version": "0.2.5",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@earendil-works/pi-agent-core": "0.75.5",
|