@qearlyao/familiar 0.2.5 → 0.4.0
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/HEARTBEAT.md +1 -1
- package/README.md +33 -0
- package/config.example.toml +4 -2
- package/dist/{agent.js → agent/factory.js} +97 -328
- package/dist/agent/payload-normalizers.js +52 -0
- package/dist/agent/session-helpers.js +86 -0
- package/dist/agent/tool-descriptions.js +4 -0
- package/dist/agent/tools.js +30 -0
- package/dist/agent/transcript-log.js +93 -0
- package/dist/cli.js +45 -15
- package/dist/config/enums.js +35 -0
- package/dist/{config.js → config/index.js} +9 -272
- package/dist/config/interpolate.js +15 -0
- package/dist/config/model-refs.js +11 -0
- package/dist/{config-overrides.js → config/overrides.js} +1 -1
- package/dist/config/readers.js +116 -0
- package/dist/{config-registry.js → config/registry.js} +27 -8
- package/dist/config/sections.js +113 -0
- package/dist/{settings.js → config/settings.js} +5 -2
- package/dist/config/types.js +1 -0
- package/dist/{chat-log.js → conversation/chat-log.js} +16 -4
- package/dist/{contact-note.js → conversation/contact-note.js} +1 -1
- package/dist/conversation/ids.js +11 -0
- package/dist/conversation/owner-identity.js +29 -0
- package/dist/discord/channel.js +32 -0
- package/dist/discord/chunking.js +163 -0
- package/dist/discord/client.js +44 -0
- package/dist/discord/commands.js +181 -0
- package/dist/discord/daemon.js +379 -0
- package/dist/discord/inbound.js +44 -0
- package/dist/discord/send.js +115 -0
- package/dist/discord/turn.js +55 -0
- package/dist/index.js +12 -11
- package/dist/lifecycle/control.js +1 -0
- package/dist/{data-retention.js → lifecycle/data-retention.js} +1 -1
- package/dist/{hot-reload.js → lifecycle/hot-reload.js} +2 -2
- package/dist/{service.js → lifecycle/service.js} +1 -0
- package/dist/media/attachment-limits.js +3 -0
- package/dist/{generated-media.js → media/generated-media.js} +1 -1
- package/dist/{image-gen.js → media/image-gen.js} +2 -2
- package/dist/{inbound-attachments.js → media/inbound-attachments.js} +47 -43
- package/dist/media/media-understanding.js +215 -0
- package/dist/memory/index/store.js +21 -17
- package/dist/memory/index/vector-codec.js +2 -2
- package/dist/memory/lcm/context-transformer.js +6 -2
- package/dist/memory/lcm/segment-manager.js +6 -2
- package/dist/memory/lcm/store/index-ids.js +6 -0
- package/dist/memory/lcm/store/inserts.js +31 -0
- package/dist/memory/lcm/store/normalizers.js +91 -0
- package/dist/memory/lcm/store/row-mappers.js +114 -0
- package/dist/memory/lcm/store/row-types.js +1 -0
- package/dist/memory/lcm/store/serialization.js +37 -0
- package/dist/memory/lcm/store/snapshots.js +73 -0
- package/dist/memory/lcm/store.js +20 -360
- package/dist/memory/lcm/summarizer.js +1 -1
- package/dist/{added-models.js → models/added-models.js} +1 -1
- package/dist/{persona.js → prompting/persona.js} +1 -1
- package/dist/runtime/agent-core.js +82 -0
- package/dist/{agent-events.js → runtime/agent-events.js} +1 -1
- package/dist/runtime/agent-work-queue.js +55 -0
- package/dist/{runtime.js → runtime/conversation-runtime.js} +91 -43
- package/dist/runtime/runtime-manager.js +51 -0
- package/dist/runtime/scheduler-runner.js +243 -0
- package/dist/{scheduler.js → runtime/scheduler.js} +4 -4
- package/dist/{browser-tools.js → tools/browser-tools.js} +24 -34
- package/dist/util/fs.js +2 -1
- package/dist/web/agent-routes.js +104 -0
- package/dist/web/auth-routes.js +39 -0
- package/dist/web/auth.js +205 -0
- package/dist/web/config-routes.js +55 -0
- package/dist/web/conversation-routes.js +122 -0
- package/dist/web/daemon.js +108 -0
- package/dist/web/diary-routes.js +88 -0
- package/dist/web/errors.js +3 -0
- package/dist/web/event-hub.js +246 -0
- package/dist/{web-http.js → web/http.js} +19 -5
- package/dist/web/memes.js +25 -0
- package/dist/web/messages.js +348 -0
- package/dist/web/multipart.js +86 -0
- package/dist/web/payloads.js +34 -0
- package/dist/web/request-context.js +25 -0
- package/dist/web/route-helpers.js +9 -0
- package/dist/web/routes.js +37 -0
- package/dist/web/runtime-actions.js +231 -0
- package/dist/web/session-store.js +161 -0
- package/dist/{web-static.js → web/static.js} +19 -14
- package/dist/web/stream.js +78 -0
- package/dist/web-tools/cache.js +42 -0
- package/dist/web-tools/config.js +16 -0
- package/dist/web-tools/fetch-providers.js +119 -0
- package/dist/web-tools/format.js +88 -0
- package/dist/web-tools/http.js +81 -0
- package/dist/web-tools/index.js +152 -0
- package/dist/web-tools/routing.js +29 -0
- package/dist/web-tools/safety.js +73 -0
- package/dist/web-tools/search-providers.js +277 -0
- package/dist/web-tools/types.js +54 -0
- package/dist/web-tools/util.js +23 -0
- package/npm-shrinkwrap.json +319 -201
- package/package.json +6 -4
- package/web/dist/assets/index-C-k4O5Dz.js +6 -0
- package/web/dist/assets/index-Dj-L9nX4.css +2 -0
- package/web/dist/assets/markdown-kaIeGxdv.js +14 -0
- package/web/dist/assets/react-Bi_azaFt.js +9 -0
- package/web/dist/assets/rolldown-runtime-S-ySWqyJ.js +1 -0
- package/web/dist/assets/ui-C12-nN_X.js +51 -0
- package/web/dist/assets/vendor-D1QXMhXm.js +16 -0
- package/web/dist/index.html +11 -3
- package/dist/discord.js +0 -1299
- package/dist/media-understanding.js +0 -120
- package/dist/web-auth.js +0 -111
- package/dist/web-tools.js +0 -941
- package/dist/web.js +0 -1209
- package/web/dist/assets/index-B23WT77N.js +0 -63
- package/web/dist/assets/index-D3MotFzN.css +0 -2
- /package/dist/{control.js → agent/types.js} +0 -0
- /package/dist/{image-derivatives.js → media/image-derivatives.js} +0 -0
- /package/dist/{tts.js → media/tts.js} +0 -0
- /package/dist/{models.js → models/index.js} +0 -0
- /package/dist/{skills.js → prompting/skills.js} +0 -0
- /package/dist/{silent-marker.js → runtime/silent-marker.js} +0 -0
- /package/dist/{web-events.js → web/events.js} +0 -0
- /package/dist/{web-types.js → web/types.js} +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { assertModelCanAuthenticate, isAllowedModel, resolveModelApiKey } from "../models/index.js";
|
|
3
|
+
const NOISY_GOOGLE_VERTEX_AUTH_DEBUG = "The user provided project/location will take precedence over the API key from the environment variables.";
|
|
4
|
+
let providerDebugFilterInstalled = false;
|
|
5
|
+
export function deriveSessionId(workspacePath, sessionKey) {
|
|
6
|
+
const digest = createHash("sha256").update(`${workspacePath}\0${sessionKey}`).digest("hex").slice(0, 32);
|
|
7
|
+
return `familiar-${digest}`;
|
|
8
|
+
}
|
|
9
|
+
export function isNoisyProviderDebug(args) {
|
|
10
|
+
return args.length === 1 && args[0] === NOISY_GOOGLE_VERTEX_AUTH_DEBUG;
|
|
11
|
+
}
|
|
12
|
+
export function installProviderDebugFilter() {
|
|
13
|
+
if (providerDebugFilterInstalled)
|
|
14
|
+
return;
|
|
15
|
+
providerDebugFilterInstalled = true;
|
|
16
|
+
const debug = console.debug;
|
|
17
|
+
console.debug = (...args) => {
|
|
18
|
+
if (isNoisyProviderDebug(args))
|
|
19
|
+
return;
|
|
20
|
+
debug.apply(console, args);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function getRequestApiKey(config, model) {
|
|
24
|
+
const apiKey = resolveModelApiKey(config, model);
|
|
25
|
+
assertModelCanAuthenticate(config, model);
|
|
26
|
+
return apiKey;
|
|
27
|
+
}
|
|
28
|
+
export function formatModel(model) {
|
|
29
|
+
return `${model.provider}/${model.id}`;
|
|
30
|
+
}
|
|
31
|
+
export function resolveModelName(value, fallback) {
|
|
32
|
+
return value ?? formatModel(fallback);
|
|
33
|
+
}
|
|
34
|
+
export function assertModelAllowed(config, ref) {
|
|
35
|
+
if (!isAllowedModel(config, ref))
|
|
36
|
+
throw new Error(`Model is not allowlisted: ${ref.key}`);
|
|
37
|
+
}
|
|
38
|
+
function extractText(message) {
|
|
39
|
+
if (!message || typeof message !== "object")
|
|
40
|
+
return "";
|
|
41
|
+
const record = message;
|
|
42
|
+
if (record.stopReason === "error" && typeof record.errorMessage === "string" && record.errorMessage.trim()) {
|
|
43
|
+
return `Model error: ${record.errorMessage}`;
|
|
44
|
+
}
|
|
45
|
+
if (!("content" in record))
|
|
46
|
+
return "";
|
|
47
|
+
const content = record.content;
|
|
48
|
+
if (typeof content === "string")
|
|
49
|
+
return content;
|
|
50
|
+
if (!Array.isArray(content))
|
|
51
|
+
return "";
|
|
52
|
+
return content
|
|
53
|
+
.filter((item) => {
|
|
54
|
+
return !!item && typeof item === "object" && item.type === "text";
|
|
55
|
+
})
|
|
56
|
+
.map((item) => item.text)
|
|
57
|
+
.join("");
|
|
58
|
+
}
|
|
59
|
+
export function getLastAssistantText(agent) {
|
|
60
|
+
for (let i = agent.state.messages.length - 1; i >= 0; i--) {
|
|
61
|
+
const message = agent.state.messages[i];
|
|
62
|
+
if (message.role === "assistant")
|
|
63
|
+
return extractText(message);
|
|
64
|
+
}
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
export function logUsage(event) {
|
|
68
|
+
if (event.type !== "message_end" || event.message.role !== "assistant")
|
|
69
|
+
return;
|
|
70
|
+
const usage = event.message.usage;
|
|
71
|
+
console.log(JSON.stringify({
|
|
72
|
+
type: "usage",
|
|
73
|
+
input: usage.input,
|
|
74
|
+
output: usage.output,
|
|
75
|
+
cacheRead: usage.cacheRead,
|
|
76
|
+
cacheWrite: usage.cacheWrite,
|
|
77
|
+
cost: usage.cost.total,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
export function userTextMessage(text, timestamp = Date.now()) {
|
|
81
|
+
return {
|
|
82
|
+
role: "user",
|
|
83
|
+
content: [{ type: "text", text }],
|
|
84
|
+
timestamp,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const BASH_DESCRIPTION = "run a bash command. defaults to the workspace; absolute paths and `~/...` reach anywhere else. returns stdout and stderr. output truncates to the last 2000 lines or 50KB, whichever hits first; full output lands in a temp file if cut. timeout in seconds optional.";
|
|
2
|
+
export const READ_DESCRIPTION = "read a file. paths resolve from the workspace, but absolute paths and `~/...` work too. text and images (jpg, png, gif, webp); images come back as attachments. text output truncates to 2000 lines or 50KB, whichever hits first — use offset and limit for long files, and keep paging until you have what you need.";
|
|
3
|
+
export const WRITE_DESCRIPTION = "write a file from scratch or replace it wholesale. creates the file if missing, overwrites if not, and makes parent directories as needed. paths resolve from the workspace; absolute and `~/...` also accepted.";
|
|
4
|
+
export const EDIT_DESCRIPTION = "edit a file with exact text replacement. each edits[].oldText must match a unique, non-overlapping slice of the original — overlapping or nested edits are rejected, so merge nearby changes into one entry rather than chaining them. don't pad oldText with large unchanged regions just to connect distant changes. paths resolve from the workspace; absolute and `~/...` also work.";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { createImageGenTool } from "../media/image-gen.js";
|
|
3
|
+
import { createTtsTool } from "../media/tts.js";
|
|
4
|
+
import { createBrowserTools } from "../tools/browser-tools.js";
|
|
5
|
+
import { createWebTools } from "../web-tools/index.js";
|
|
6
|
+
import { BASH_DESCRIPTION, EDIT_DESCRIPTION, READ_DESCRIPTION, WRITE_DESCRIPTION } from "./tool-descriptions.js";
|
|
7
|
+
export function createFamiliarTools(config, mediaSink, referenceAttachments = () => [], memoryService) {
|
|
8
|
+
const bashTool = createBashTool(config.workspacePath);
|
|
9
|
+
bashTool.description = BASH_DESCRIPTION;
|
|
10
|
+
const readTool = createReadTool(config.workspacePath);
|
|
11
|
+
readTool.description = READ_DESCRIPTION;
|
|
12
|
+
const writeTool = createWriteTool(config.workspacePath);
|
|
13
|
+
writeTool.description = WRITE_DESCRIPTION;
|
|
14
|
+
const editTool = createEditTool(config.workspacePath);
|
|
15
|
+
editTool.description = EDIT_DESCRIPTION;
|
|
16
|
+
return [
|
|
17
|
+
bashTool,
|
|
18
|
+
readTool,
|
|
19
|
+
writeTool,
|
|
20
|
+
editTool,
|
|
21
|
+
createTtsTool(config, mediaSink),
|
|
22
|
+
...(config.imageGen.enabled ? [createImageGenTool(config, mediaSink, { referenceAttachments })] : []),
|
|
23
|
+
...createWebTools(config),
|
|
24
|
+
...createBrowserTools(config, mediaSink),
|
|
25
|
+
...(memoryService?.memoryTools() ?? []),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
export function setReferenceAttachments(session, attachments = []) {
|
|
29
|
+
session.referenceAttachments.splice(0, session.referenceAttachments.length, ...attachments);
|
|
30
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { appendFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { isEnoent } from "../util/fs.js";
|
|
4
|
+
function dailyLogPath(dataDir, streamName, now = new Date()) {
|
|
5
|
+
const date = now.toISOString().slice(0, 10);
|
|
6
|
+
return resolve(dataDir, streamName, `${date}.jsonl`);
|
|
7
|
+
}
|
|
8
|
+
async function appendJsonl(path, record) {
|
|
9
|
+
await mkdir(dirname(path), { recursive: true });
|
|
10
|
+
await appendFile(path, `${JSON.stringify(record)}\n`, "utf8");
|
|
11
|
+
}
|
|
12
|
+
export function writePayloadLog(config, record) {
|
|
13
|
+
appendJsonl(dailyLogPath(config.workspace.dataDir, "payloads"), record).catch((err) => console.error("payload log write failed", err));
|
|
14
|
+
}
|
|
15
|
+
export function writeTranscriptLog(config, record) {
|
|
16
|
+
appendJsonl(dailyLogPath(config.workspace.dataDir, "transcripts"), record).catch((err) => console.error("transcript log write failed", err));
|
|
17
|
+
}
|
|
18
|
+
function isStoredMessageRecord(value) {
|
|
19
|
+
if (!value || typeof value !== "object")
|
|
20
|
+
return false;
|
|
21
|
+
const record = value;
|
|
22
|
+
return typeof record.ts === "string" && typeof record.sessionId === "string" && !!record.message;
|
|
23
|
+
}
|
|
24
|
+
function isStoredResetRecord(value) {
|
|
25
|
+
if (!value || typeof value !== "object")
|
|
26
|
+
return false;
|
|
27
|
+
const record = value;
|
|
28
|
+
return record.type === "reset" && typeof record.ts === "string" && typeof record.sessionId === "string";
|
|
29
|
+
}
|
|
30
|
+
function isStoredSupersedeRecord(value) {
|
|
31
|
+
if (!value || typeof value !== "object")
|
|
32
|
+
return false;
|
|
33
|
+
const record = value;
|
|
34
|
+
return (record.type === "supersede" &&
|
|
35
|
+
typeof record.ts === "string" &&
|
|
36
|
+
typeof record.sessionId === "string" &&
|
|
37
|
+
typeof record.messageTimestamp === "number");
|
|
38
|
+
}
|
|
39
|
+
export async function loadStoredMessages(dataDir, sessionId) {
|
|
40
|
+
const transcriptsDir = resolve(dataDir, "transcripts");
|
|
41
|
+
let files;
|
|
42
|
+
try {
|
|
43
|
+
files = await readdir(transcriptsDir);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (isEnoent(error))
|
|
47
|
+
return [];
|
|
48
|
+
console.error("transcript history read failed", error);
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const jsonlFiles = files.filter((entry) => entry.endsWith(".jsonl")).sort();
|
|
52
|
+
const records = [];
|
|
53
|
+
for (const file of jsonlFiles) {
|
|
54
|
+
const path = resolve(transcriptsDir, file);
|
|
55
|
+
let contents;
|
|
56
|
+
try {
|
|
57
|
+
contents = await readFile(path, "utf8");
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(`transcript file read failed: ${path}`, error);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
for (const [index, line] of contents.split(/\r?\n/).entries()) {
|
|
64
|
+
if (!line.trim())
|
|
65
|
+
continue;
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(line);
|
|
68
|
+
if (!isStoredMessageRecord(parsed) && !isStoredResetRecord(parsed) && !isStoredSupersedeRecord(parsed)) {
|
|
69
|
+
console.error(`skipping malformed transcript line: ${path}:${index + 1}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (parsed.sessionId !== sessionId)
|
|
73
|
+
continue;
|
|
74
|
+
records.push(parsed);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error(`skipping unparsable transcript line: ${path}:${index + 1}`, error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
records.sort((a, b) => a.ts.localeCompare(b.ts));
|
|
82
|
+
let lastResetIndex = -1;
|
|
83
|
+
for (let index = records.length - 1; index >= 0; index--) {
|
|
84
|
+
const record = records[index];
|
|
85
|
+
if (record && "type" in record && record.type === "reset") {
|
|
86
|
+
lastResetIndex = index;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const activeRecords = lastResetIndex >= 0 ? records.slice(lastResetIndex + 1) : records;
|
|
91
|
+
const superseded = new Set(activeRecords.flatMap((record) => "type" in record && record.type === "supersede" ? [record.messageTimestamp] : []));
|
|
92
|
+
return activeRecords.flatMap((record) => "message" in record && !superseded.has(record.message.timestamp) ? [record.message] : []);
|
|
93
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { copyFile, cp, mkdir } from "node:fs/promises";
|
|
3
|
+
import { copyFile, cp, mkdir, readFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { config as loadDotenv } from "dotenv";
|
|
8
|
-
import { createFamiliarAgent } from "./agent.js";
|
|
9
|
-
import { loadConfig } from "./config.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { createFamiliarAgent } from "./agent/factory.js";
|
|
9
|
+
import { loadConfig } from "./config/index.js";
|
|
10
|
+
import { loadSettingsStore } from "./config/settings.js";
|
|
11
|
+
import { loadOwnerIdentity } from "./conversation/owner-identity.js";
|
|
12
|
+
import { startDiscordDaemon } from "./discord/daemon.js";
|
|
13
|
+
import { runDataRetention } from "./lifecycle/data-retention.js";
|
|
14
|
+
import { startWorkspaceHotReload } from "./lifecycle/hot-reload.js";
|
|
15
|
+
import { formatServiceResult, installService, serviceStatus, uninstallService, upgradeFamiliar, } from "./lifecycle/service.js";
|
|
16
|
+
import { cleanupGeneratedAttachments } from "./media/generated-media.js";
|
|
14
17
|
import { memoryHelp, runMemoryOperator } from "./memory/operator.js";
|
|
15
18
|
import { createMemoryService } from "./memory/service.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { startWebDaemon } from "./web.js";
|
|
19
|
+
import { createAgentCore } from "./runtime/agent-core.js";
|
|
20
|
+
import { startWebDaemon } from "./web/daemon.js";
|
|
19
21
|
const SOURCE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
20
22
|
const PROJECT_ROOT = resolve(SOURCE_DIR, "..");
|
|
21
23
|
const DEFAULT_WORKSPACE_PATH = resolve(homedir(), ".familiar");
|
|
@@ -76,6 +78,11 @@ function isMemoryHelp(args) {
|
|
|
76
78
|
const command = args[0];
|
|
77
79
|
return !command || command === "help" || command === "--help";
|
|
78
80
|
}
|
|
81
|
+
async function packageVersion() {
|
|
82
|
+
const raw = await readFile(resolve(PROJECT_ROOT, "package.json"), "utf8");
|
|
83
|
+
const packageJson = JSON.parse(raw);
|
|
84
|
+
return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
85
|
+
}
|
|
79
86
|
async function initWorkspace(workspaceInput) {
|
|
80
87
|
const workspacePath = resolveWorkspaceInput(workspaceInput);
|
|
81
88
|
await mkdir(workspacePath, { recursive: true });
|
|
@@ -119,6 +126,7 @@ async function runDaemon(workspaceInput) {
|
|
|
119
126
|
memoryService.watchDiaries();
|
|
120
127
|
const familiarAgent = await createFamiliarAgent(config, settings, memoryService, { reloadConfig });
|
|
121
128
|
const hotReload = startWorkspaceHotReload({ workspacePath: config.workspacePath, familiarAgent });
|
|
129
|
+
const agentCore = createAgentCore({ config, familiarAgent, memoryService });
|
|
122
130
|
let stopping = false;
|
|
123
131
|
let discordDaemon;
|
|
124
132
|
let webDaemon;
|
|
@@ -129,6 +137,7 @@ async function runDaemon(workspaceInput) {
|
|
|
129
137
|
console.log("Stopping familiar");
|
|
130
138
|
hotReload.close();
|
|
131
139
|
await Promise.all([webDaemon?.stop(), discordDaemon?.stop()]);
|
|
140
|
+
await agentCore.stop();
|
|
132
141
|
memoryService.close();
|
|
133
142
|
process.exit(exitCode);
|
|
134
143
|
};
|
|
@@ -137,10 +146,21 @@ async function runDaemon(workspaceInput) {
|
|
|
137
146
|
setTimeout(() => void stop(75), RESTART_EXIT_DELAY_MS);
|
|
138
147
|
return "Restart requested. If Familiar is managed by launchd/systemd, it should come back automatically; otherwise run familiar run again.";
|
|
139
148
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
const identity = await loadOwnerIdentity(config.workspace.dataDir);
|
|
150
|
+
const token = config.discord.token;
|
|
151
|
+
if (!identity && !token) {
|
|
152
|
+
throw new Error("First-time setup needs a DISCORD_TOKEN to establish owner identity. Set DISCORD_TOKEN and run again.");
|
|
153
|
+
}
|
|
154
|
+
// The scheduler starts with the first session source to arrive: the cached identity
|
|
155
|
+
// here, or the live Discord connection below when there is no cache yet.
|
|
156
|
+
if (identity)
|
|
157
|
+
await agentCore.useCachedIdentity(identity);
|
|
158
|
+
webDaemon = await startWebDaemon(config, familiarAgent, agentCore, { restart: requestRestart });
|
|
159
|
+
if (token) {
|
|
160
|
+
discordDaemon = startDiscordDaemon(config, token, familiarAgent, settings, memoryService, agentCore, {
|
|
161
|
+
restart: requestRestart,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
144
164
|
console.log(`familiar running for workspace ${config.workspacePath}`);
|
|
145
165
|
console.log("agent sessions are created per channel");
|
|
146
166
|
console.log(`settings=${settings.path}`);
|
|
@@ -151,6 +171,8 @@ async function runDaemon(workspaceInput) {
|
|
|
151
171
|
function usage() {
|
|
152
172
|
return [
|
|
153
173
|
"Usage:",
|
|
174
|
+
" familiar --help",
|
|
175
|
+
" familiar --version",
|
|
154
176
|
" familiar init [workspace]",
|
|
155
177
|
" familiar run [workspace]",
|
|
156
178
|
" familiar memory [workspace] <subcommand>",
|
|
@@ -164,6 +186,14 @@ function usage() {
|
|
|
164
186
|
}
|
|
165
187
|
async function main() {
|
|
166
188
|
const [, , command, workspace, ...rest] = process.argv;
|
|
189
|
+
if (!command || command === "--help" || command === "-h") {
|
|
190
|
+
console.log(usage());
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (command === "--version" || command === "-v") {
|
|
194
|
+
console.log(await packageVersion());
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
167
197
|
if (command === "init") {
|
|
168
198
|
await initWorkspace(workspace);
|
|
169
199
|
return;
|
|
@@ -200,7 +230,7 @@ async function main() {
|
|
|
200
230
|
return;
|
|
201
231
|
}
|
|
202
232
|
if (command === "upgrade") {
|
|
203
|
-
console.log("Upgrading @qearlyao/familiar globally...");
|
|
233
|
+
console.log("Upgrading @qearlyao/familiar and OpenCLI globally...");
|
|
204
234
|
await upgradeFamiliar(resolveWorkspaceInput(workspace));
|
|
205
235
|
return;
|
|
206
236
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const CACHE_RETENTIONS = ["none", "short", "long"];
|
|
2
|
+
export const THINKING_LEVELS = [
|
|
3
|
+
"off",
|
|
4
|
+
"minimal",
|
|
5
|
+
"low",
|
|
6
|
+
"medium",
|
|
7
|
+
"high",
|
|
8
|
+
"xhigh",
|
|
9
|
+
];
|
|
10
|
+
export const DISCORD_REPLY_MODES = ["plain", "reply"];
|
|
11
|
+
export const DISCORD_CHUNK_MODES = ["simple", "paragraph", "newline"];
|
|
12
|
+
export const DISCORD_DISPATCH_MODES = ["steer", "queue", "collect"];
|
|
13
|
+
export const DISCORD_CHANNEL_TRIGGERS = ["mention", "always"];
|
|
14
|
+
export const CRON_FREQUENCIES = [
|
|
15
|
+
"once",
|
|
16
|
+
"hourly",
|
|
17
|
+
"daily",
|
|
18
|
+
"weekly",
|
|
19
|
+
"monthly",
|
|
20
|
+
];
|
|
21
|
+
export const CRON_DELIVERY_MODES = ["queue", "follow_up"];
|
|
22
|
+
export const WEB_AUTH_MODES = ["tailscale-only", "bearer", "public-2fa"];
|
|
23
|
+
export const TTS_PROVIDERS = ["elevenlabs"];
|
|
24
|
+
export const IMAGE_GEN_APIS = ["openrouter-images"];
|
|
25
|
+
export const MEDIA_UNDERSTANDING_PROVIDERS = [
|
|
26
|
+
"groq",
|
|
27
|
+
"google",
|
|
28
|
+
];
|
|
29
|
+
export const MEMORY_EMBEDDING_FORMATS = [
|
|
30
|
+
"gemini",
|
|
31
|
+
"openai",
|
|
32
|
+
"voyage",
|
|
33
|
+
];
|
|
34
|
+
export const BROWSER_BACKENDS = ["opencli", "browser-harness"];
|
|
35
|
+
export const BROWSER_WINDOW_MODES = ["foreground", "background"];
|