talon-agent 1.6.1 → 1.7.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/README.md +1 -1
- package/package.json +2 -2
- package/src/__tests__/chat-settings.test.ts +47 -36
- package/src/__tests__/claude-sdk-models.test.ts +157 -0
- package/src/__tests__/claude-sdk-options.test.ts +118 -0
- package/src/__tests__/config.test.ts +112 -8
- package/src/__tests__/dream.test.ts +3 -3
- package/src/__tests__/fuzz.test.ts +15 -15
- package/src/__tests__/plugin.test.ts +155 -2
- package/src/__tests__/telegram-helpers.test.ts +113 -0
- package/src/backend/claude-sdk/models.ts +385 -68
- package/src/backend/claude-sdk/options.ts +6 -4
- package/src/backend/claude-sdk/stream.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/core/models.ts +49 -5
- package/src/core/plugin.ts +207 -118
- package/src/frontend/telegram/callbacks.ts +16 -10
- package/src/frontend/telegram/commands.ts +19 -10
- package/src/frontend/telegram/helpers.ts +78 -7
- package/src/plugins/playwright/index.ts +54 -20
- package/src/util/config.ts +98 -15
|
@@ -10,9 +10,16 @@
|
|
|
10
10
|
* "browser": "chromium", // optional, default "chromium"
|
|
11
11
|
* "headless": true // optional, default true
|
|
12
12
|
* }
|
|
13
|
+
*
|
|
14
|
+
* For Camoufox (anti-detect browser):
|
|
15
|
+
* "playwright": {
|
|
16
|
+
* "enabled": true,
|
|
17
|
+
* "browser": "firefox",
|
|
18
|
+
* "endpointFile": "/home/dylan/camoufox-endpoint.txt"
|
|
19
|
+
* }
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
|
-
import { existsSync } from "node:fs";
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
23
|
import { resolve } from "node:path";
|
|
17
24
|
import type { TalonPlugin } from "../../core/plugin.js";
|
|
18
25
|
import { log } from "../../util/log.js";
|
|
@@ -20,29 +27,51 @@ import { log } from "../../util/log.js";
|
|
|
20
27
|
export function createPlaywrightPlugin(config: {
|
|
21
28
|
browser?: string;
|
|
22
29
|
headless?: boolean;
|
|
30
|
+
endpoint?: string;
|
|
31
|
+
endpointFile?: string;
|
|
23
32
|
}): TalonPlugin {
|
|
24
33
|
const browser = config.browser ?? "chromium";
|
|
25
34
|
const headless = config.headless !== false; // default true
|
|
26
35
|
|
|
36
|
+
// Resolve endpoint: direct string or read from file
|
|
37
|
+
let endpoint = config.endpoint;
|
|
38
|
+
if (!endpoint && config.endpointFile) {
|
|
39
|
+
try {
|
|
40
|
+
endpoint = readFileSync(config.endpointFile, "utf-8").trim();
|
|
41
|
+
} catch {
|
|
42
|
+
log(
|
|
43
|
+
"playwright",
|
|
44
|
+
`Warning: could not read endpoint file ${config.endpointFile}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
// Resolve path from Talon's node_modules
|
|
28
50
|
const mcpBin = resolve(
|
|
29
51
|
import.meta.dirname ?? ".",
|
|
30
52
|
"../../../node_modules/@playwright/mcp/cli.js",
|
|
31
53
|
);
|
|
32
54
|
|
|
33
|
-
const args = [
|
|
55
|
+
const args: string[] = [];
|
|
34
56
|
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
if (endpoint) {
|
|
58
|
+
// Connect to existing browser (e.g. Camoufox websocket server)
|
|
59
|
+
args.push("--endpoint", endpoint);
|
|
60
|
+
} else {
|
|
61
|
+
args.push("--no-sandbox");
|
|
62
|
+
|
|
63
|
+
if (headless) {
|
|
64
|
+
args.push("--headless");
|
|
65
|
+
}
|
|
38
66
|
|
|
39
|
-
|
|
40
|
-
|
|
67
|
+
if (browser !== "chromium") {
|
|
68
|
+
args.push("--browser", browser);
|
|
69
|
+
}
|
|
41
70
|
}
|
|
42
71
|
|
|
43
72
|
return {
|
|
44
73
|
name: "playwright",
|
|
45
|
-
description:
|
|
74
|
+
description: `Browser automation via Playwright MCP (${endpoint ? "Camoufox" : browser})`,
|
|
46
75
|
version: "1.0.0",
|
|
47
76
|
|
|
48
77
|
mcpServer: {
|
|
@@ -53,17 +82,19 @@ export function createPlaywrightPlugin(config: {
|
|
|
53
82
|
validateConfig() {
|
|
54
83
|
const errors: string[] = [];
|
|
55
84
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
if (!endpoint) {
|
|
86
|
+
const validBrowsers = [
|
|
87
|
+
"chromium",
|
|
88
|
+
"chrome",
|
|
89
|
+
"firefox",
|
|
90
|
+
"webkit",
|
|
91
|
+
"msedge",
|
|
92
|
+
];
|
|
93
|
+
if (!validBrowsers.includes(browser)) {
|
|
94
|
+
errors.push(
|
|
95
|
+
`Invalid browser "${browser}". Valid options: ${validBrowsers.join(", ")}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
if (!existsSync(mcpBin)) {
|
|
@@ -76,7 +107,10 @@ export function createPlaywrightPlugin(config: {
|
|
|
76
107
|
},
|
|
77
108
|
|
|
78
109
|
async init() {
|
|
79
|
-
log(
|
|
110
|
+
log(
|
|
111
|
+
"playwright",
|
|
112
|
+
`Ready (${endpoint ? `Camoufox @ ${endpoint}` : `${browser}, headless=${headless}`})`,
|
|
113
|
+
);
|
|
80
114
|
},
|
|
81
115
|
};
|
|
82
116
|
}
|
package/src/util/config.ts
CHANGED
|
@@ -14,19 +14,110 @@ import { log } from "./log.js";
|
|
|
14
14
|
|
|
15
15
|
// ── Config schema ───────────────────────────────────────────────────────────
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/** Path-based Talon plugin (loaded as a Node module). */
|
|
18
|
+
const pluginPathSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
path: z.string(),
|
|
21
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
|
|
25
|
+
/** Standalone MCP server (command + args, not a Talon plugin module). */
|
|
26
|
+
const pluginMcpSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
name: z.string(),
|
|
29
|
+
command: z.string(),
|
|
30
|
+
args: z.array(z.string()).optional(),
|
|
31
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
32
|
+
})
|
|
33
|
+
.strict();
|
|
34
|
+
|
|
35
|
+
const pluginEntrySchema = z
|
|
36
|
+
.object({
|
|
37
|
+
path: z.string().optional(),
|
|
38
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
39
|
+
name: z.string().optional(),
|
|
40
|
+
command: z.string().optional(),
|
|
41
|
+
args: z.array(z.string()).optional(),
|
|
42
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
43
|
+
})
|
|
44
|
+
.strict()
|
|
45
|
+
.superRefine((value, ctx) => {
|
|
46
|
+
const hasPath = value.path !== undefined;
|
|
47
|
+
const hasMcpFields =
|
|
48
|
+
value.name !== undefined ||
|
|
49
|
+
value.command !== undefined ||
|
|
50
|
+
value.args !== undefined ||
|
|
51
|
+
value.env !== undefined;
|
|
52
|
+
|
|
53
|
+
if (hasPath && hasMcpFields) {
|
|
54
|
+
ctx.addIssue({
|
|
55
|
+
code: z.ZodIssueCode.custom,
|
|
56
|
+
message:
|
|
57
|
+
"Plugin entry must use exactly one format: either 'path' (with optional 'config') or MCP fields ('name', 'command', optional 'args'/'env'), but not both.",
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!hasPath && !hasMcpFields) {
|
|
63
|
+
ctx.addIssue({
|
|
64
|
+
code: z.ZodIssueCode.custom,
|
|
65
|
+
message:
|
|
66
|
+
"Plugin entry must provide either 'path' or both 'name' and 'command'.",
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (hasMcpFields) {
|
|
72
|
+
if (value.config !== undefined) {
|
|
73
|
+
ctx.addIssue({
|
|
74
|
+
code: z.ZodIssueCode.custom,
|
|
75
|
+
path: ["config"],
|
|
76
|
+
message: "MCP plugin entries cannot include 'config'.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (value.name === undefined) {
|
|
81
|
+
ctx.addIssue({
|
|
82
|
+
code: z.ZodIssueCode.custom,
|
|
83
|
+
path: ["name"],
|
|
84
|
+
message: "MCP plugin entries must include 'name'.",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (value.command === undefined) {
|
|
89
|
+
ctx.addIssue({
|
|
90
|
+
code: z.ZodIssueCode.custom,
|
|
91
|
+
path: ["command"],
|
|
92
|
+
message: "MCP plugin entries must include 'command'.",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.pipe(z.union([pluginPathSchema, pluginMcpSchema]));
|
|
21
100
|
|
|
22
101
|
const frontendEnum = z.enum(["telegram", "terminal", "teams"]);
|
|
23
102
|
|
|
103
|
+
const playwrightConfigSchema = z.object({
|
|
104
|
+
enabled: z.boolean().default(false),
|
|
105
|
+
/** Browser engine: chromium (default), chrome, firefox, webkit, msedge */
|
|
106
|
+
browser: z.string().optional(),
|
|
107
|
+
/** Run headless (default: true) */
|
|
108
|
+
headless: z.boolean().default(true),
|
|
109
|
+
/** Connect to an existing browser websocket endpoint. */
|
|
110
|
+
endpoint: z.string().optional(),
|
|
111
|
+
/** Read the browser websocket endpoint from a file. */
|
|
112
|
+
endpointFile: z.string().optional(),
|
|
113
|
+
});
|
|
114
|
+
|
|
24
115
|
const configSchema = z.object({
|
|
25
116
|
frontend: z.union([frontendEnum, z.array(frontendEnum)]).default("telegram"),
|
|
26
117
|
botToken: z.string().optional(),
|
|
27
118
|
backend: z.enum(["claude", "opencode"]).default("claude"),
|
|
28
119
|
claudeBinary: z.string().optional(),
|
|
29
|
-
model: z.string().default("
|
|
120
|
+
model: z.string().default("default"),
|
|
30
121
|
dreamModel: z.string().optional(), // Model used for background memory consolidation (defaults to main model)
|
|
31
122
|
maxMessageLength: z.number().int().min(100).default(4000),
|
|
32
123
|
concurrency: z.number().int().min(1).max(20).default(1),
|
|
@@ -64,15 +155,7 @@ const configSchema = z.object({
|
|
|
64
155
|
.optional(),
|
|
65
156
|
|
|
66
157
|
// Playwright — headless browser automation via MCP
|
|
67
|
-
playwright:
|
|
68
|
-
.object({
|
|
69
|
-
enabled: z.boolean().default(false),
|
|
70
|
-
/** Browser engine: chromium (default), chrome, firefox, webkit, msedge */
|
|
71
|
-
browser: z.string().optional(),
|
|
72
|
-
/** Run headless (default: true) */
|
|
73
|
-
headless: z.boolean().default(true),
|
|
74
|
-
})
|
|
75
|
-
.optional(),
|
|
158
|
+
playwright: playwrightConfigSchema.optional(),
|
|
76
159
|
|
|
77
160
|
// Display name shown in terminal UI (defaults to "Talon")
|
|
78
161
|
botDisplayName: z.string().default("Talon"),
|
|
@@ -104,7 +187,7 @@ const CONFIG_FILE = pathFiles.config;
|
|
|
104
187
|
|
|
105
188
|
const DEFAULT_CONFIG = {
|
|
106
189
|
botToken: "",
|
|
107
|
-
model: "
|
|
190
|
+
model: "default",
|
|
108
191
|
maxMessageLength: 4000,
|
|
109
192
|
concurrency: 1,
|
|
110
193
|
pulse: true,
|