@qearlyao/familiar 0.2.0 → 0.2.1
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 +10 -0
- package/dist/browser-tools.js +28 -4
- package/dist/memory/lcm/context-transformer.js +14 -3
- package/dist/memory/lcm/context.js +15 -4
- package/package.json +1 -1
- package/web/dist/assets/index-BPZQbZh5.js +61 -0
- package/web/dist/familiar.svg +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-CUvbIJKO.js +0 -60
package/README.md
CHANGED
|
@@ -8,6 +8,16 @@ optional real-browser control in one workspace.
|
|
|
8
8
|
This project is still early. The current release is meant for trusted friends who
|
|
9
9
|
are comfortable editing a config file and running a long-lived Node process.
|
|
10
10
|
|
|
11
|
+
## Credits
|
|
12
|
+
|
|
13
|
+
Familiar builds on the [pi](https://github.com/earendil-works/pi)
|
|
14
|
+
stack, including `@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`, and
|
|
15
|
+
`@earendil-works/pi-coding-agent`.
|
|
16
|
+
|
|
17
|
+
It also borrows ideas and structure from
|
|
18
|
+
[lossless-claw](https://github.com/Martian-Engineering/lossless-claw) and
|
|
19
|
+
[pi-lcm-memory](https://github.com/sharkone/pi-lcm-memory).
|
|
20
|
+
|
|
11
21
|
## Requirements
|
|
12
22
|
|
|
13
23
|
- Node.js 22 or newer. Node.js 24 LTS is recommended and is the primary tested runtime.
|
package/dist/browser-tools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { stat } from "node:fs/promises";
|
|
4
|
+
import { platform } from "node:os";
|
|
4
5
|
import { basename, extname, resolve } from "node:path";
|
|
5
6
|
import { Type } from "typebox";
|
|
6
7
|
import { ensureBrowserScreenshotsDir } from "./generated-media.js";
|
|
@@ -102,12 +103,34 @@ const browserSchema = Type.Object({
|
|
|
102
103
|
description: "Site-command positional arguments, in OpenCLI usage order, such as twitter post text.",
|
|
103
104
|
})),
|
|
104
105
|
}, { additionalProperties: false });
|
|
106
|
+
function quoteWindowsShellArg(value) {
|
|
107
|
+
const escaped = value
|
|
108
|
+
.replace(/%/g, "%%")
|
|
109
|
+
.replace(/(\\*)"/g, '$1$1\\"')
|
|
110
|
+
.replace(/(\\+)$/g, "$1$1");
|
|
111
|
+
return `"${escaped}"`;
|
|
112
|
+
}
|
|
113
|
+
function buildSpawnInvocation(spec, currentPlatform = platform(), comSpec = process.env.ComSpec ?? "cmd.exe") {
|
|
114
|
+
const options = {
|
|
115
|
+
stdio: [spec.stdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
116
|
+
env: spec.env,
|
|
117
|
+
};
|
|
118
|
+
if (currentPlatform !== "win32")
|
|
119
|
+
return { command: spec.command, args: spec.args, options };
|
|
120
|
+
const commandLine = [spec.command, ...spec.args].map(quoteWindowsShellArg).join(" ");
|
|
121
|
+
return {
|
|
122
|
+
command: comSpec,
|
|
123
|
+
args: ["/d", "/s", "/c", commandLine],
|
|
124
|
+
options: {
|
|
125
|
+
...options,
|
|
126
|
+
windowsVerbatimArguments: true,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
105
130
|
function defaultBrowserRunner() {
|
|
106
131
|
return (spec, options) => new Promise((resolvePromise, reject) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
env: spec.env,
|
|
110
|
-
});
|
|
132
|
+
const invocation = buildSpawnInvocation(spec);
|
|
133
|
+
const child = spawn(invocation.command, invocation.args, invocation.options);
|
|
111
134
|
const timeout = setTimeout(() => {
|
|
112
135
|
child.kill("SIGTERM");
|
|
113
136
|
reject(new Error(`Browser command timed out after ${options.timeoutMs}ms.`));
|
|
@@ -685,6 +708,7 @@ export function createBrowserTools(config, mediaSink, runner = defaultBrowserRun
|
|
|
685
708
|
];
|
|
686
709
|
}
|
|
687
710
|
export const __browserToolsTest = {
|
|
711
|
+
buildSpawnInvocation,
|
|
688
712
|
buildHarnessSpec,
|
|
689
713
|
buildPageArgs,
|
|
690
714
|
buildRunSpec,
|
|
@@ -599,8 +599,13 @@ function lcmRecordPartsFromAgentMessage(message) {
|
|
|
599
599
|
return [];
|
|
600
600
|
const parts = [];
|
|
601
601
|
for (const item of content) {
|
|
602
|
-
if (item.type === "text")
|
|
603
|
-
parts.push({
|
|
602
|
+
if (item.type === "text") {
|
|
603
|
+
parts.push({
|
|
604
|
+
kind: "text",
|
|
605
|
+
text: item.text,
|
|
606
|
+
...(item.textSignature ? { signature: item.textSignature } : {}),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
604
609
|
else if (item.type === "thinking") {
|
|
605
610
|
parts.push({
|
|
606
611
|
kind: "thinking",
|
|
@@ -609,7 +614,13 @@ function lcmRecordPartsFromAgentMessage(message) {
|
|
|
609
614
|
});
|
|
610
615
|
}
|
|
611
616
|
else if (item.type === "toolCall") {
|
|
612
|
-
parts.push({
|
|
617
|
+
parts.push({
|
|
618
|
+
kind: "tool_call",
|
|
619
|
+
toolCallId: item.id,
|
|
620
|
+
toolName: item.name,
|
|
621
|
+
arguments: item.arguments,
|
|
622
|
+
...(item.thoughtSignature ? { signature: item.thoughtSignature } : {}),
|
|
623
|
+
});
|
|
613
624
|
}
|
|
614
625
|
else if (item.type === "image") {
|
|
615
626
|
parts.push({ kind: "text", text: `[image: ${item.mimeType}]` });
|
|
@@ -114,13 +114,18 @@ function estimateUserMessageTokens(message) {
|
|
|
114
114
|
function estimateAssistantMessageTokens(message) {
|
|
115
115
|
let tokens = MESSAGE_OVERHEAD_TOKENS;
|
|
116
116
|
for (const block of message.content) {
|
|
117
|
-
if (block.type === "text")
|
|
117
|
+
if (block.type === "text") {
|
|
118
118
|
tokens += estimateTextTokens(block.text);
|
|
119
|
-
|
|
119
|
+
tokens += estimateTextTokens(block.textSignature ?? "");
|
|
120
|
+
}
|
|
121
|
+
else if (block.type === "thinking") {
|
|
120
122
|
tokens += estimateTextTokens(block.thinking);
|
|
123
|
+
tokens += estimateTextTokens(block.thinkingSignature ?? "");
|
|
124
|
+
}
|
|
121
125
|
else if (block.type === "toolCall") {
|
|
122
126
|
tokens += estimateTextTokens(block.name);
|
|
123
127
|
tokens += estimateJsonTokens(block.arguments);
|
|
128
|
+
tokens += estimateTextTokens(block.thoughtSignature ?? "");
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
return tokens;
|
|
@@ -319,8 +324,13 @@ function structuredLcmRecordToAgentMessage(record, timestamp) {
|
|
|
319
324
|
function structuredAssistantContent(parts) {
|
|
320
325
|
const content = [];
|
|
321
326
|
for (const part of parts) {
|
|
322
|
-
if (part.kind === "text" && part.text)
|
|
323
|
-
content.push({
|
|
327
|
+
if (part.kind === "text" && part.text) {
|
|
328
|
+
content.push({
|
|
329
|
+
type: "text",
|
|
330
|
+
text: part.text,
|
|
331
|
+
...(part.signature ? { textSignature: part.signature } : {}),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
324
334
|
else if (part.kind === "thinking" && part.text) {
|
|
325
335
|
content.push({
|
|
326
336
|
type: "thinking",
|
|
@@ -334,6 +344,7 @@ function structuredAssistantContent(parts) {
|
|
|
334
344
|
id: part.toolCallId,
|
|
335
345
|
name: part.toolName,
|
|
336
346
|
arguments: normalizeToolArguments(part.arguments),
|
|
347
|
+
...(part.signature ? { thoughtSignature: part.signature } : {}),
|
|
337
348
|
});
|
|
338
349
|
}
|
|
339
350
|
}
|