open-agents-ai 0.4.0 → 0.5.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 +22 -0
- package/dist/index.js +658 -85
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -160,6 +160,28 @@ The agent follows an iterative fix loop:
|
|
|
160
160
|
-V, --version Show version
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
### Voice Feedback (TTS)
|
|
164
|
+
|
|
165
|
+
The agent can speak what it's doing using neural TTS voices. Enable it in the interactive REPL:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
/voice # Toggle voice on/off (default: GLaDOS)
|
|
169
|
+
/voice glados # Switch to GLaDOS voice
|
|
170
|
+
/voice overwatch # Switch to Overwatch voice
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
On first enable, the agent auto-downloads the ONNX voice model (~50MB) and installs `onnxruntime-node` in `~/.open-agents/voice/`. For best quality, install `espeak-ng`:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Ubuntu/Debian
|
|
177
|
+
sudo apt install espeak-ng
|
|
178
|
+
|
|
179
|
+
# macOS
|
|
180
|
+
brew install espeak-ng
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
When enabled, the agent speaks brief descriptions of each tool call ("Reading auth.ts", "Running tests", "Editing config.js") through your system speakers.
|
|
184
|
+
|
|
163
185
|
### Configuration
|
|
164
186
|
|
|
165
187
|
Config priority: CLI flags > environment variables > `~/.open-agents/config.json` > defaults.
|
package/dist/index.js
CHANGED
|
@@ -1116,21 +1116,29 @@ var init_tool_executor = __esm({
|
|
|
1116
1116
|
});
|
|
1117
1117
|
|
|
1118
1118
|
// packages/execution/dist/tools/shell.js
|
|
1119
|
-
import {
|
|
1120
|
-
|
|
1121
|
-
var execFileAsync, ShellTool;
|
|
1119
|
+
import { spawn } from "node:child_process";
|
|
1120
|
+
var ShellTool;
|
|
1122
1121
|
var init_shell = __esm({
|
|
1123
1122
|
"packages/execution/dist/tools/shell.js"() {
|
|
1124
1123
|
"use strict";
|
|
1125
|
-
execFileAsync = promisify(execFile);
|
|
1126
1124
|
ShellTool = class {
|
|
1127
1125
|
name = "shell";
|
|
1128
|
-
description = "Execute a shell command in the project working directory";
|
|
1126
|
+
description = "Execute a shell command in the project working directory. Commands run non-interactively (CI=true). For commands that need input, use the stdin parameter or pass non-interactive flags (--yes, --no-input).";
|
|
1129
1127
|
parameters = {
|
|
1130
1128
|
type: "object",
|
|
1131
1129
|
properties: {
|
|
1132
|
-
command: {
|
|
1133
|
-
|
|
1130
|
+
command: {
|
|
1131
|
+
type: "string",
|
|
1132
|
+
description: "The shell command to execute"
|
|
1133
|
+
},
|
|
1134
|
+
timeout: {
|
|
1135
|
+
type: "number",
|
|
1136
|
+
description: "Timeout in milliseconds (default: 30000)"
|
|
1137
|
+
},
|
|
1138
|
+
stdin: {
|
|
1139
|
+
type: "string",
|
|
1140
|
+
description: "Text to write to the command's standard input. Use newlines to separate multiple inputs. For arrow-key prompts, prefer --yes or non-interactive flags instead."
|
|
1141
|
+
}
|
|
1134
1142
|
},
|
|
1135
1143
|
required: ["command"]
|
|
1136
1144
|
};
|
|
@@ -1143,29 +1151,89 @@ var init_shell = __esm({
|
|
|
1143
1151
|
async execute(args) {
|
|
1144
1152
|
const command = args["command"];
|
|
1145
1153
|
const timeout = args["timeout"] ?? this.defaultTimeout;
|
|
1154
|
+
const stdinInput = args["stdin"];
|
|
1146
1155
|
const start = performance.now();
|
|
1147
|
-
|
|
1148
|
-
const
|
|
1156
|
+
return new Promise((resolve12) => {
|
|
1157
|
+
const child = spawn("bash", ["-c", command], {
|
|
1149
1158
|
cwd: this.workingDir,
|
|
1150
|
-
|
|
1151
|
-
|
|
1159
|
+
env: {
|
|
1160
|
+
...process.env,
|
|
1161
|
+
// Non-interactive mode: libraries like prompts, inquirer,
|
|
1162
|
+
// create-next-app, and many Node CLI tools check CI to skip
|
|
1163
|
+
// interactive prompts and use defaults instead.
|
|
1164
|
+
CI: "true",
|
|
1165
|
+
NONINTERACTIVE: "1",
|
|
1166
|
+
// Disable color in most tools to get clean output
|
|
1167
|
+
NO_COLOR: "1",
|
|
1168
|
+
FORCE_COLOR: "0"
|
|
1169
|
+
},
|
|
1170
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1152
1171
|
});
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1172
|
+
let stdout = "";
|
|
1173
|
+
let stderr = "";
|
|
1174
|
+
let killed = false;
|
|
1175
|
+
const maxBuf = 1024 * 1024;
|
|
1176
|
+
const timer = setTimeout(() => {
|
|
1177
|
+
killed = true;
|
|
1178
|
+
child.kill("SIGTERM");
|
|
1179
|
+
setTimeout(() => {
|
|
1180
|
+
try {
|
|
1181
|
+
child.kill("SIGKILL");
|
|
1182
|
+
} catch {
|
|
1183
|
+
}
|
|
1184
|
+
}, 5e3);
|
|
1185
|
+
}, timeout);
|
|
1186
|
+
child.stdout.on("data", (data) => {
|
|
1187
|
+
stdout += data.toString();
|
|
1188
|
+
if (stdout.length > maxBuf) {
|
|
1189
|
+
stdout = stdout.slice(0, maxBuf);
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
child.stderr.on("data", (data) => {
|
|
1193
|
+
stderr += data.toString();
|
|
1194
|
+
if (stderr.length > maxBuf) {
|
|
1195
|
+
stderr = stderr.slice(0, maxBuf);
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
if (stdinInput) {
|
|
1199
|
+
child.stdin.write(stdinInput);
|
|
1200
|
+
}
|
|
1201
|
+
child.stdin.end();
|
|
1202
|
+
child.on("close", (code) => {
|
|
1203
|
+
clearTimeout(timer);
|
|
1204
|
+
const durationMs = performance.now() - start;
|
|
1205
|
+
if (killed) {
|
|
1206
|
+
const combined = stdout + stderr;
|
|
1207
|
+
const looksInteractive = /\? .+[›>]|y\/n|yes\/no|\(Y\/n\)|\[y\/N\]/i.test(combined);
|
|
1208
|
+
const hint = looksInteractive ? " The command appears to be waiting for interactive input. Use non-interactive flags (e.g., --yes, --no-input) or provide input via the stdin parameter." : "";
|
|
1209
|
+
resolve12({
|
|
1210
|
+
success: false,
|
|
1211
|
+
output: stdout,
|
|
1212
|
+
error: `Command timed out after ${timeout}ms.${hint}`,
|
|
1213
|
+
durationMs
|
|
1214
|
+
});
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
const success = code === 0;
|
|
1218
|
+
resolve12({
|
|
1219
|
+
success,
|
|
1220
|
+
output: stdout + (stderr && success ? `
|
|
1156
1221
|
STDERR:
|
|
1157
1222
|
${stderr}` : ""),
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1223
|
+
error: success ? void 0 : stderr || `Exit code ${code}`,
|
|
1224
|
+
durationMs
|
|
1225
|
+
});
|
|
1226
|
+
});
|
|
1227
|
+
child.on("error", (err) => {
|
|
1228
|
+
clearTimeout(timer);
|
|
1229
|
+
resolve12({
|
|
1230
|
+
success: false,
|
|
1231
|
+
output: stdout,
|
|
1232
|
+
error: err.message,
|
|
1233
|
+
durationMs: performance.now() - start
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1169
1237
|
}
|
|
1170
1238
|
};
|
|
1171
1239
|
}
|
|
@@ -1275,14 +1343,14 @@ var init_file_write = __esm({
|
|
|
1275
1343
|
});
|
|
1276
1344
|
|
|
1277
1345
|
// packages/execution/dist/tools/grep-search.js
|
|
1278
|
-
import { execFile
|
|
1279
|
-
import { promisify
|
|
1346
|
+
import { execFile } from "node:child_process";
|
|
1347
|
+
import { promisify } from "node:util";
|
|
1280
1348
|
import { resolve as resolve3 } from "node:path";
|
|
1281
|
-
var
|
|
1349
|
+
var execFileAsync, MAX_OUTPUT_LINES, GrepSearchTool;
|
|
1282
1350
|
var init_grep_search = __esm({
|
|
1283
1351
|
"packages/execution/dist/tools/grep-search.js"() {
|
|
1284
1352
|
"use strict";
|
|
1285
|
-
|
|
1353
|
+
execFileAsync = promisify(execFile);
|
|
1286
1354
|
MAX_OUTPUT_LINES = 100;
|
|
1287
1355
|
GrepSearchTool = class {
|
|
1288
1356
|
name = "grep_search";
|
|
@@ -1323,7 +1391,7 @@ var init_grep_search = __esm({
|
|
|
1323
1391
|
rgArgs.push("--glob", include);
|
|
1324
1392
|
}
|
|
1325
1393
|
rgArgs.push(pattern, searchPath);
|
|
1326
|
-
const { stdout } = await
|
|
1394
|
+
const { stdout } = await execFileAsync("rg", rgArgs, {
|
|
1327
1395
|
cwd: this.workingDir,
|
|
1328
1396
|
maxBuffer: 1024 * 1024
|
|
1329
1397
|
});
|
|
@@ -1334,7 +1402,7 @@ var init_grep_search = __esm({
|
|
|
1334
1402
|
grepArgs.push(`--include=${include}`);
|
|
1335
1403
|
}
|
|
1336
1404
|
grepArgs.push(pattern, searchPath);
|
|
1337
|
-
const { stdout } = await
|
|
1405
|
+
const { stdout } = await execFileAsync("grep", grepArgs, {
|
|
1338
1406
|
cwd: this.workingDir,
|
|
1339
1407
|
maxBuffer: 1024 * 1024
|
|
1340
1408
|
});
|
|
@@ -1369,7 +1437,7 @@ var init_grep_search = __esm({
|
|
|
1369
1437
|
}
|
|
1370
1438
|
async #isRipgrepAvailable() {
|
|
1371
1439
|
try {
|
|
1372
|
-
await
|
|
1440
|
+
await execFileAsync("rg", ["--version"], { timeout: 2e3 });
|
|
1373
1441
|
return true;
|
|
1374
1442
|
} catch {
|
|
1375
1443
|
return false;
|
|
@@ -1380,14 +1448,14 @@ var init_grep_search = __esm({
|
|
|
1380
1448
|
});
|
|
1381
1449
|
|
|
1382
1450
|
// packages/execution/dist/tools/glob-find.js
|
|
1383
|
-
import { execFile as
|
|
1384
|
-
import { promisify as
|
|
1451
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
1452
|
+
import { promisify as promisify2 } from "node:util";
|
|
1385
1453
|
import { resolve as resolve4 } from "node:path";
|
|
1386
|
-
var
|
|
1454
|
+
var execFileAsync2, MAX_RESULTS, GlobFindTool;
|
|
1387
1455
|
var init_glob_find = __esm({
|
|
1388
1456
|
"packages/execution/dist/tools/glob-find.js"() {
|
|
1389
1457
|
"use strict";
|
|
1390
|
-
|
|
1458
|
+
execFileAsync2 = promisify2(execFile2);
|
|
1391
1459
|
MAX_RESULTS = 50;
|
|
1392
1460
|
GlobFindTool = class {
|
|
1393
1461
|
name = "find_files";
|
|
@@ -1415,7 +1483,7 @@ var init_glob_find = __esm({
|
|
|
1415
1483
|
const searchPath = resolve4(this.workingDir, args["path"] ?? ".");
|
|
1416
1484
|
const start = performance.now();
|
|
1417
1485
|
try {
|
|
1418
|
-
const { stdout } = await
|
|
1486
|
+
const { stdout } = await execFileAsync2("find", [
|
|
1419
1487
|
searchPath,
|
|
1420
1488
|
"-type",
|
|
1421
1489
|
"f",
|
|
@@ -4087,13 +4155,13 @@ var init_plannerRunner = __esm({
|
|
|
4087
4155
|
});
|
|
4088
4156
|
|
|
4089
4157
|
// packages/retrieval/dist/grep-search.js
|
|
4090
|
-
import { execFile as
|
|
4091
|
-
import { promisify as
|
|
4092
|
-
var
|
|
4158
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
4159
|
+
import { promisify as promisify3 } from "node:util";
|
|
4160
|
+
var execFileAsync3, GrepSearch;
|
|
4093
4161
|
var init_grep_search2 = __esm({
|
|
4094
4162
|
"packages/retrieval/dist/grep-search.js"() {
|
|
4095
4163
|
"use strict";
|
|
4096
|
-
|
|
4164
|
+
execFileAsync3 = promisify3(execFile3);
|
|
4097
4165
|
GrepSearch = class {
|
|
4098
4166
|
rootDir;
|
|
4099
4167
|
constructor(rootDir) {
|
|
@@ -4115,7 +4183,7 @@ var init_grep_search2 = __esm({
|
|
|
4115
4183
|
}
|
|
4116
4184
|
args.push(pattern, this.rootDir);
|
|
4117
4185
|
try {
|
|
4118
|
-
const { stdout } = await
|
|
4186
|
+
const { stdout } = await execFileAsync3("rg", args, {
|
|
4119
4187
|
maxBuffer: 10 * 1024 * 1024
|
|
4120
4188
|
});
|
|
4121
4189
|
return this.parseRipgrepOutput(stdout);
|
|
@@ -4190,8 +4258,8 @@ var init_code_retriever = __esm({
|
|
|
4190
4258
|
});
|
|
4191
4259
|
|
|
4192
4260
|
// packages/retrieval/dist/lexicalSearch.js
|
|
4193
|
-
import { execFile as
|
|
4194
|
-
import { promisify as
|
|
4261
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
4262
|
+
import { promisify as promisify4 } from "node:util";
|
|
4195
4263
|
import { readFile as readFile6, readdir, stat } from "node:fs/promises";
|
|
4196
4264
|
import { join as join10, extname as extname2 } from "node:path";
|
|
4197
4265
|
async function searchByPath(pathPattern, options) {
|
|
@@ -4235,7 +4303,7 @@ async function runSearch(pattern, kind, options) {
|
|
|
4235
4303
|
}
|
|
4236
4304
|
async function isRipgrepAvailable() {
|
|
4237
4305
|
try {
|
|
4238
|
-
await
|
|
4306
|
+
await execFileAsync4("rg", ["--version"], { timeout: 2e3 });
|
|
4239
4307
|
return true;
|
|
4240
4308
|
} catch {
|
|
4241
4309
|
return false;
|
|
@@ -4255,7 +4323,7 @@ async function searchWithRipgrep(pattern, kind, options) {
|
|
|
4255
4323
|
args.push(pattern, options.rootDir);
|
|
4256
4324
|
let stdout;
|
|
4257
4325
|
try {
|
|
4258
|
-
const result = await
|
|
4326
|
+
const result = await execFileAsync4("rg", args, {
|
|
4259
4327
|
maxBuffer: 20 * 1024 * 1024,
|
|
4260
4328
|
timeout: 1e4
|
|
4261
4329
|
});
|
|
@@ -4365,11 +4433,11 @@ function matchesGlob(filePath, glob2) {
|
|
|
4365
4433
|
return false;
|
|
4366
4434
|
}
|
|
4367
4435
|
}
|
|
4368
|
-
var
|
|
4436
|
+
var execFileAsync4, DEFAULT_INCLUDE_GLOBS, DEFAULT_EXCLUDE_GLOBS, DEFAULT_MAX_MATCHES, ALWAYS_SKIP, ALL_CODE_EXTS;
|
|
4369
4437
|
var init_lexicalSearch = __esm({
|
|
4370
4438
|
"packages/retrieval/dist/lexicalSearch.js"() {
|
|
4371
4439
|
"use strict";
|
|
4372
|
-
|
|
4440
|
+
execFileAsync4 = promisify4(execFile4);
|
|
4373
4441
|
DEFAULT_INCLUDE_GLOBS = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.py"];
|
|
4374
4442
|
DEFAULT_EXCLUDE_GLOBS = ["node_modules", "dist", ".git", "build", "coverage"];
|
|
4375
4443
|
DEFAULT_MAX_MATCHES = 50;
|
|
@@ -5115,9 +5183,9 @@ var init_verifierRunner = __esm({
|
|
|
5115
5183
|
async executeTests(patch, repoRoot) {
|
|
5116
5184
|
if (patch.testsToRun.length === 0)
|
|
5117
5185
|
return "(no tests specified)";
|
|
5118
|
-
const { execFile:
|
|
5119
|
-
const { promisify:
|
|
5120
|
-
const
|
|
5186
|
+
const { execFile: execFile5 } = await import("node:child_process");
|
|
5187
|
+
const { promisify: promisify5 } = await import("node:util");
|
|
5188
|
+
const execFileAsync5 = promisify5(execFile5);
|
|
5121
5189
|
const outputs = [];
|
|
5122
5190
|
const workDir = this.options.workingDir || repoRoot;
|
|
5123
5191
|
for (const cmd of patch.testsToRun.slice(0, 3)) {
|
|
@@ -5126,7 +5194,7 @@ var init_verifierRunner = __esm({
|
|
|
5126
5194
|
const [bin, ...args] = parts;
|
|
5127
5195
|
if (!bin)
|
|
5128
5196
|
continue;
|
|
5129
|
-
const { stdout, stderr } = await
|
|
5197
|
+
const { stdout, stderr } = await execFileAsync5(bin, args, {
|
|
5130
5198
|
cwd: workDir,
|
|
5131
5199
|
timeout: 6e4,
|
|
5132
5200
|
maxBuffer: 1024 * 1024
|
|
@@ -5701,7 +5769,7 @@ var init_agenticRunner = __esm({
|
|
|
5701
5769
|
- file_edit: Make a precise string replacement in a file (preferred over rewriting). Uses old_string/new_string.
|
|
5702
5770
|
- find_files: Find files by name pattern (glob). Searches recursively, excludes node_modules/.git.
|
|
5703
5771
|
- grep_search: Search file contents with regex. Returns matching lines with paths and line numbers.
|
|
5704
|
-
- shell: Execute any shell command (tests, builds, git, npm, etc.)
|
|
5772
|
+
- shell: Execute any shell command (tests, builds, git, npm, etc.). Supports stdin parameter for input. Commands run with CI=true for non-interactive mode.
|
|
5705
5773
|
- list_directory: List files in a directory with types and sizes
|
|
5706
5774
|
- web_search: Search the web for documentation or solutions
|
|
5707
5775
|
- web_fetch: Fetch a web page and extract text content (for docs, MDN, w3schools.com, etc.)
|
|
@@ -5752,6 +5820,15 @@ When a test or build fails:
|
|
|
5752
5820
|
8. If it fails with the SAME error, try a different approach
|
|
5753
5821
|
9. After 3 failed attempts at the same error, use web_search for solutions
|
|
5754
5822
|
|
|
5823
|
+
## Interactive Commands
|
|
5824
|
+
|
|
5825
|
+
Commands run non-interactively (CI=true). When running scaffolding tools:
|
|
5826
|
+
- ALWAYS add non-interactive flags: --yes, --no-input, --defaults, etc.
|
|
5827
|
+
- For npx create-next-app: use --yes (skips all prompts, uses defaults)
|
|
5828
|
+
- For npm init: use -y
|
|
5829
|
+
- If a command needs specific answers, use the stdin parameter
|
|
5830
|
+
- If a command times out, it likely hit an interactive prompt \u2014 retry with --yes
|
|
5831
|
+
|
|
5755
5832
|
## Context Efficiency
|
|
5756
5833
|
|
|
5757
5834
|
- Use grep_search to find specific code instead of reading many files
|
|
@@ -6210,6 +6287,8 @@ function renderSlashHelp() {
|
|
|
6210
6287
|
["/endpoint <url> --auth <t>", "Set endpoint with Bearer auth"],
|
|
6211
6288
|
["/config", "Show current configuration"],
|
|
6212
6289
|
["/update", "Check for updates and auto-install"],
|
|
6290
|
+
["/voice", "Toggle TTS voice feedback (GLaDOS)"],
|
|
6291
|
+
["/voice <model>", "Set voice: glados, overwatch"],
|
|
6213
6292
|
["/verbose", "Toggle verbose mode"],
|
|
6214
6293
|
["/clear", "Clear the screen"],
|
|
6215
6294
|
["/help", "Show this help"],
|
|
@@ -6267,8 +6346,10 @@ function formatToolArgs(toolName, args) {
|
|
|
6267
6346
|
case "file_write":
|
|
6268
6347
|
case "file_edit":
|
|
6269
6348
|
return String(args["path"] ?? "");
|
|
6270
|
-
case "shell":
|
|
6271
|
-
|
|
6349
|
+
case "shell": {
|
|
6350
|
+
const cmd = truncStr(String(args["command"] ?? ""), 60);
|
|
6351
|
+
return args["stdin"] ? `${cmd} ${c2.dim("(with stdin)")}` : cmd;
|
|
6352
|
+
}
|
|
6272
6353
|
case "grep_search":
|
|
6273
6354
|
return `${c2.yellow(String(args["pattern"] ?? ""))}${args["path"] ? ` in ${args["path"]}` : ""}`;
|
|
6274
6355
|
case "find_files":
|
|
@@ -6440,6 +6521,16 @@ async function handleSlashCommand(input, ctx) {
|
|
|
6440
6521
|
case "upgrade":
|
|
6441
6522
|
await handleUpdate();
|
|
6442
6523
|
return "handled";
|
|
6524
|
+
case "voice": {
|
|
6525
|
+
if (arg) {
|
|
6526
|
+
const msg = await ctx.voiceSetModel(arg);
|
|
6527
|
+
renderInfo(msg);
|
|
6528
|
+
} else {
|
|
6529
|
+
const msg = await ctx.voiceToggle();
|
|
6530
|
+
renderInfo(msg);
|
|
6531
|
+
}
|
|
6532
|
+
return "handled";
|
|
6533
|
+
}
|
|
6443
6534
|
default:
|
|
6444
6535
|
renderWarning(`Unknown command: /${cmd}. Type /help for available commands.`);
|
|
6445
6536
|
return "handled";
|
|
@@ -6552,11 +6643,11 @@ async function handleEndpoint(arg, ctx) {
|
|
|
6552
6643
|
async function handleUpdate() {
|
|
6553
6644
|
let currentVersion = "0.0.0";
|
|
6554
6645
|
try {
|
|
6555
|
-
const { createRequire:
|
|
6646
|
+
const { createRequire: createRequire3 } = await import("node:module");
|
|
6556
6647
|
const { fileURLToPath: fileURLToPath2 } = await import("node:url");
|
|
6557
|
-
const { dirname: dirname4, join:
|
|
6558
|
-
const require2 =
|
|
6559
|
-
const pkgPath =
|
|
6648
|
+
const { dirname: dirname4, join: join18 } = await import("node:path");
|
|
6649
|
+
const require2 = createRequire3(import.meta.url);
|
|
6650
|
+
const pkgPath = join18(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
6560
6651
|
const pkg = require2(pkgPath);
|
|
6561
6652
|
currentVersion = pkg.version ?? "0.0.0";
|
|
6562
6653
|
} catch {
|
|
@@ -6916,9 +7007,9 @@ async function doSetup(config, rl) {
|
|
|
6916
7007
|
`PARAMETER num_predict 16384`,
|
|
6917
7008
|
`PARAMETER stop "<|endoftext|>"`
|
|
6918
7009
|
].join("\n");
|
|
6919
|
-
const
|
|
6920
|
-
mkdirSync3(
|
|
6921
|
-
const modelfilePath = join12(
|
|
7010
|
+
const modelDir2 = join12(homedir3(), ".open-agents", "models");
|
|
7011
|
+
mkdirSync3(modelDir2, { recursive: true });
|
|
7012
|
+
const modelfilePath = join12(modelDir2, `Modelfile.${customName}`);
|
|
6922
7013
|
writeFileSync3(modelfilePath, modelfileContent + "\n", "utf8");
|
|
6923
7014
|
process.stdout.write(` ${c2.dim("Creating model...")} `);
|
|
6924
7015
|
execSync7(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
@@ -7003,6 +7094,472 @@ var init_setup = __esm({
|
|
|
7003
7094
|
}
|
|
7004
7095
|
});
|
|
7005
7096
|
|
|
7097
|
+
// packages/cli/dist/tui/voice.js
|
|
7098
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, readFileSync as readFileSync6, unlinkSync } from "node:fs";
|
|
7099
|
+
import { join as join13 } from "node:path";
|
|
7100
|
+
import { homedir as homedir4, tmpdir, platform } from "node:os";
|
|
7101
|
+
import { execSync as execSync8, spawn as nodeSpawn } from "node:child_process";
|
|
7102
|
+
import { createRequire } from "node:module";
|
|
7103
|
+
function modelDir(id) {
|
|
7104
|
+
return join13(MODELS_DIR, id);
|
|
7105
|
+
}
|
|
7106
|
+
function modelOnnxPath(id) {
|
|
7107
|
+
return join13(modelDir(id), "model.onnx");
|
|
7108
|
+
}
|
|
7109
|
+
function modelConfigPath(id) {
|
|
7110
|
+
return join13(modelDir(id), "config.json");
|
|
7111
|
+
}
|
|
7112
|
+
function describeToolCall(toolName, args) {
|
|
7113
|
+
const path = args["path"];
|
|
7114
|
+
const file = path ? path.split("/").pop() ?? path : "";
|
|
7115
|
+
switch (toolName) {
|
|
7116
|
+
case "file_read":
|
|
7117
|
+
return `Reading ${file}`;
|
|
7118
|
+
case "file_write":
|
|
7119
|
+
return `Writing ${file}`;
|
|
7120
|
+
case "file_edit":
|
|
7121
|
+
return `Editing ${file}`;
|
|
7122
|
+
case "shell": {
|
|
7123
|
+
const cmd = String(args["command"] ?? "");
|
|
7124
|
+
if (/npm\s+test|vitest|jest|mocha/.test(cmd))
|
|
7125
|
+
return "Running tests";
|
|
7126
|
+
if (/npm\s+run\s+build|tsc|esbuild/.test(cmd))
|
|
7127
|
+
return "Building project";
|
|
7128
|
+
if (/npm\s+install|pnpm\s+install/.test(cmd))
|
|
7129
|
+
return "Installing dependencies";
|
|
7130
|
+
if (/git\s+/.test(cmd))
|
|
7131
|
+
return "Running git command";
|
|
7132
|
+
if (/npm\s+run\s+lint|eslint|biome/.test(cmd))
|
|
7133
|
+
return "Running linter";
|
|
7134
|
+
if (cmd.length > 40)
|
|
7135
|
+
return "Running shell command";
|
|
7136
|
+
return `Running ${cmd.slice(0, 30)}`;
|
|
7137
|
+
}
|
|
7138
|
+
case "grep_search":
|
|
7139
|
+
return `Searching for ${args["pattern"] ?? "pattern"}`;
|
|
7140
|
+
case "find_files":
|
|
7141
|
+
return `Finding files matching ${args["pattern"] ?? "pattern"}`;
|
|
7142
|
+
case "list_directory":
|
|
7143
|
+
return `Listing directory ${file || "contents"}`;
|
|
7144
|
+
case "web_search":
|
|
7145
|
+
return `Searching the web`;
|
|
7146
|
+
case "web_fetch":
|
|
7147
|
+
return `Fetching web page`;
|
|
7148
|
+
case "memory_read":
|
|
7149
|
+
return `Reading from memory`;
|
|
7150
|
+
case "memory_write":
|
|
7151
|
+
return `Saving to memory`;
|
|
7152
|
+
case "task_complete":
|
|
7153
|
+
return String(args["summary"] ?? "Task complete");
|
|
7154
|
+
case "batch_edit":
|
|
7155
|
+
return `Editing multiple files`;
|
|
7156
|
+
case "codebase_map":
|
|
7157
|
+
return `Mapping project structure`;
|
|
7158
|
+
case "diagnostic":
|
|
7159
|
+
return `Running diagnostics`;
|
|
7160
|
+
case "git_info":
|
|
7161
|
+
return `Checking git status`;
|
|
7162
|
+
case "aiwg_setup":
|
|
7163
|
+
return `Setting up development framework`;
|
|
7164
|
+
case "aiwg_health":
|
|
7165
|
+
return `Analyzing project health`;
|
|
7166
|
+
case "aiwg_workflow":
|
|
7167
|
+
return `Running workflow command`;
|
|
7168
|
+
default:
|
|
7169
|
+
return `Using ${toolName}`;
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
function describeToolResult(toolName, success) {
|
|
7173
|
+
if (toolName === "task_complete")
|
|
7174
|
+
return "";
|
|
7175
|
+
return success ? "Done" : "That failed, trying to fix it";
|
|
7176
|
+
}
|
|
7177
|
+
function formatBytes2(bytes) {
|
|
7178
|
+
if (bytes < 1024)
|
|
7179
|
+
return `${bytes}B`;
|
|
7180
|
+
if (bytes < 1024 * 1024)
|
|
7181
|
+
return `${(bytes / 1024).toFixed(0)}KB`;
|
|
7182
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
7183
|
+
}
|
|
7184
|
+
var VOICE_MODELS, VOICE_DIR, MODELS_DIR, VoiceEngine;
|
|
7185
|
+
var init_voice = __esm({
|
|
7186
|
+
"packages/cli/dist/tui/voice.js"() {
|
|
7187
|
+
"use strict";
|
|
7188
|
+
init_render();
|
|
7189
|
+
VOICE_MODELS = {
|
|
7190
|
+
glados: {
|
|
7191
|
+
id: "glados",
|
|
7192
|
+
label: "GLaDOS",
|
|
7193
|
+
onnxUrl: "https://raw.githubusercontent.com/robit-man/EGG/main/voice/glados_piper_medium.onnx",
|
|
7194
|
+
configUrl: "https://raw.githubusercontent.com/robit-man/EGG/main/voice/glados_piper_medium.onnx.json"
|
|
7195
|
+
},
|
|
7196
|
+
overwatch: {
|
|
7197
|
+
id: "overwatch",
|
|
7198
|
+
label: "Overwatch",
|
|
7199
|
+
onnxUrl: "https://raw.githubusercontent.com/robit-man/combine_overwatch_onnx/main/overwatch.onnx",
|
|
7200
|
+
configUrl: "https://raw.githubusercontent.com/robit-man/combine_overwatch_onnx/main/overwatch.onnx.json"
|
|
7201
|
+
}
|
|
7202
|
+
};
|
|
7203
|
+
VOICE_DIR = join13(homedir4(), ".open-agents", "voice");
|
|
7204
|
+
MODELS_DIR = join13(VOICE_DIR, "models");
|
|
7205
|
+
VoiceEngine = class {
|
|
7206
|
+
enabled = false;
|
|
7207
|
+
modelId = "glados";
|
|
7208
|
+
ready = false;
|
|
7209
|
+
session = null;
|
|
7210
|
+
// ort.InferenceSession
|
|
7211
|
+
ort = null;
|
|
7212
|
+
// onnxruntime-node module
|
|
7213
|
+
config = null;
|
|
7214
|
+
currentPlayback = null;
|
|
7215
|
+
speakQueue = [];
|
|
7216
|
+
speaking = false;
|
|
7217
|
+
hasEspeak = false;
|
|
7218
|
+
// -------------------------------------------------------------------------
|
|
7219
|
+
// Public API
|
|
7220
|
+
// -------------------------------------------------------------------------
|
|
7221
|
+
async toggle() {
|
|
7222
|
+
if (this.enabled) {
|
|
7223
|
+
this.enabled = false;
|
|
7224
|
+
this.killPlayback();
|
|
7225
|
+
return "Voice feedback disabled.";
|
|
7226
|
+
}
|
|
7227
|
+
try {
|
|
7228
|
+
await this.ensureRuntime();
|
|
7229
|
+
await this.ensureModel(this.modelId);
|
|
7230
|
+
await this.loadSession();
|
|
7231
|
+
this.enabled = true;
|
|
7232
|
+
this.ready = true;
|
|
7233
|
+
return `Voice feedback enabled (${VOICE_MODELS[this.modelId]?.label ?? this.modelId}).`;
|
|
7234
|
+
} catch (err) {
|
|
7235
|
+
return `Failed to enable voice: ${err instanceof Error ? err.message : String(err)}`;
|
|
7236
|
+
}
|
|
7237
|
+
}
|
|
7238
|
+
async setModel(id) {
|
|
7239
|
+
const key = id.toLowerCase();
|
|
7240
|
+
if (!VOICE_MODELS[key]) {
|
|
7241
|
+
return `Unknown voice model: "${id}". Available: ${Object.keys(VOICE_MODELS).join(", ")}`;
|
|
7242
|
+
}
|
|
7243
|
+
this.modelId = key;
|
|
7244
|
+
this.session = null;
|
|
7245
|
+
this.config = null;
|
|
7246
|
+
this.ready = false;
|
|
7247
|
+
if (this.enabled) {
|
|
7248
|
+
try {
|
|
7249
|
+
await this.ensureModel(key);
|
|
7250
|
+
await this.loadSession();
|
|
7251
|
+
this.ready = true;
|
|
7252
|
+
return `Switched to ${VOICE_MODELS[key].label} voice.`;
|
|
7253
|
+
} catch (err) {
|
|
7254
|
+
this.enabled = false;
|
|
7255
|
+
return `Failed to load ${key}: ${err instanceof Error ? err.message : String(err)}`;
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
return `Voice model set to ${VOICE_MODELS[key].label}. Enable with /voice.`;
|
|
7259
|
+
}
|
|
7260
|
+
/**
|
|
7261
|
+
* Speak text asynchronously (non-blocking).
|
|
7262
|
+
* Queues if already speaking; drops if queue gets too deep.
|
|
7263
|
+
*/
|
|
7264
|
+
speak(text) {
|
|
7265
|
+
if (!this.enabled || !this.ready)
|
|
7266
|
+
return;
|
|
7267
|
+
if (this.speakQueue.length >= 3) {
|
|
7268
|
+
this.speakQueue.length = 0;
|
|
7269
|
+
}
|
|
7270
|
+
this.speakQueue.push(text);
|
|
7271
|
+
if (!this.speaking) {
|
|
7272
|
+
this.drainQueue().catch(() => {
|
|
7273
|
+
});
|
|
7274
|
+
}
|
|
7275
|
+
}
|
|
7276
|
+
dispose() {
|
|
7277
|
+
this.enabled = false;
|
|
7278
|
+
this.killPlayback();
|
|
7279
|
+
this.session = null;
|
|
7280
|
+
this.ort = null;
|
|
7281
|
+
this.config = null;
|
|
7282
|
+
}
|
|
7283
|
+
// -------------------------------------------------------------------------
|
|
7284
|
+
// Queue drain
|
|
7285
|
+
// -------------------------------------------------------------------------
|
|
7286
|
+
async drainQueue() {
|
|
7287
|
+
this.speaking = true;
|
|
7288
|
+
while (this.speakQueue.length > 0) {
|
|
7289
|
+
const text = this.speakQueue.pop();
|
|
7290
|
+
this.speakQueue.length = 0;
|
|
7291
|
+
try {
|
|
7292
|
+
await this.synthesizeAndPlay(text);
|
|
7293
|
+
} catch {
|
|
7294
|
+
}
|
|
7295
|
+
}
|
|
7296
|
+
this.speaking = false;
|
|
7297
|
+
}
|
|
7298
|
+
// -------------------------------------------------------------------------
|
|
7299
|
+
// Synthesis pipeline
|
|
7300
|
+
// -------------------------------------------------------------------------
|
|
7301
|
+
async synthesizeAndPlay(text) {
|
|
7302
|
+
if (!this.session || !this.config || !this.ort)
|
|
7303
|
+
return;
|
|
7304
|
+
const phonemeIds = this.textToPhonemeIds(text);
|
|
7305
|
+
if (phonemeIds.length === 0)
|
|
7306
|
+
return;
|
|
7307
|
+
const inputLength = phonemeIds.length;
|
|
7308
|
+
const inputTensor = new this.ort.Tensor("int64", BigInt64Array.from(phonemeIds.map((id) => BigInt(id))), [1, inputLength]);
|
|
7309
|
+
const lengthTensor = new this.ort.Tensor("int64", BigInt64Array.from([BigInt(inputLength)]), [1]);
|
|
7310
|
+
const scalesTensor = new this.ort.Tensor("float32", Float32Array.from([0.667, 1, 0.8]), [3]);
|
|
7311
|
+
const feeds = {
|
|
7312
|
+
input: inputTensor,
|
|
7313
|
+
input_lengths: lengthTensor,
|
|
7314
|
+
scales: scalesTensor
|
|
7315
|
+
};
|
|
7316
|
+
if (this.config.num_speakers > 1) {
|
|
7317
|
+
feeds["sid"] = new this.ort.Tensor("int64", BigInt64Array.from([BigInt(0)]), [1]);
|
|
7318
|
+
}
|
|
7319
|
+
const result = await this.session.run(feeds);
|
|
7320
|
+
const audioData = result["output"].data;
|
|
7321
|
+
if (audioData.length === 0)
|
|
7322
|
+
return;
|
|
7323
|
+
const wavPath = join13(tmpdir(), `oa-voice-${Date.now()}.wav`);
|
|
7324
|
+
this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
|
|
7325
|
+
await this.playWav(wavPath);
|
|
7326
|
+
try {
|
|
7327
|
+
unlinkSync(wavPath);
|
|
7328
|
+
} catch {
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
// -------------------------------------------------------------------------
|
|
7332
|
+
// Phonemization
|
|
7333
|
+
// -------------------------------------------------------------------------
|
|
7334
|
+
textToPhonemeIds(text) {
|
|
7335
|
+
const map = this.config.phoneme_id_map;
|
|
7336
|
+
let phonemes;
|
|
7337
|
+
if (this.hasEspeak) {
|
|
7338
|
+
try {
|
|
7339
|
+
const voice = this.config.espeak?.voice ?? "en-us";
|
|
7340
|
+
phonemes = execSync8(`espeak-ng --ipa -q --sep="" -v ${voice} "${text.replace(/"/g, '\\"')}"`, { encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7341
|
+
} catch {
|
|
7342
|
+
phonemes = text.toLowerCase();
|
|
7343
|
+
}
|
|
7344
|
+
} else {
|
|
7345
|
+
phonemes = text.toLowerCase();
|
|
7346
|
+
}
|
|
7347
|
+
const ids = [];
|
|
7348
|
+
if (map["^"])
|
|
7349
|
+
ids.push(...map["^"]);
|
|
7350
|
+
for (const char of phonemes) {
|
|
7351
|
+
if (map[char]) {
|
|
7352
|
+
ids.push(...map[char]);
|
|
7353
|
+
if (map["_"])
|
|
7354
|
+
ids.push(...map["_"]);
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
if (map["$"])
|
|
7358
|
+
ids.push(...map["$"]);
|
|
7359
|
+
return ids;
|
|
7360
|
+
}
|
|
7361
|
+
// -------------------------------------------------------------------------
|
|
7362
|
+
// WAV encoder (PCM 16-bit mono)
|
|
7363
|
+
// -------------------------------------------------------------------------
|
|
7364
|
+
writeWav(samples, sampleRate, path) {
|
|
7365
|
+
const numChannels = 1;
|
|
7366
|
+
const bitsPerSample = 16;
|
|
7367
|
+
const byteRate = sampleRate * numChannels * (bitsPerSample / 8);
|
|
7368
|
+
const blockAlign = numChannels * (bitsPerSample / 8);
|
|
7369
|
+
const int16 = new Int16Array(samples.length);
|
|
7370
|
+
for (let i = 0; i < samples.length; i++) {
|
|
7371
|
+
const s = Math.max(-1, Math.min(1, samples[i]));
|
|
7372
|
+
int16[i] = s < 0 ? s * 32768 : s * 32767;
|
|
7373
|
+
}
|
|
7374
|
+
const dataSize = int16.length * 2;
|
|
7375
|
+
const buffer = Buffer.alloc(44 + dataSize);
|
|
7376
|
+
buffer.write("RIFF", 0);
|
|
7377
|
+
buffer.writeUInt32LE(36 + dataSize, 4);
|
|
7378
|
+
buffer.write("WAVE", 8);
|
|
7379
|
+
buffer.write("fmt ", 12);
|
|
7380
|
+
buffer.writeUInt32LE(16, 16);
|
|
7381
|
+
buffer.writeUInt16LE(1, 20);
|
|
7382
|
+
buffer.writeUInt16LE(numChannels, 22);
|
|
7383
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
7384
|
+
buffer.writeUInt32LE(byteRate, 28);
|
|
7385
|
+
buffer.writeUInt16LE(blockAlign, 32);
|
|
7386
|
+
buffer.writeUInt16LE(bitsPerSample, 34);
|
|
7387
|
+
buffer.write("data", 36);
|
|
7388
|
+
buffer.writeUInt32LE(dataSize, 40);
|
|
7389
|
+
Buffer.from(int16.buffer, int16.byteOffset, int16.byteLength).copy(buffer, 44);
|
|
7390
|
+
writeFileSync4(path, buffer);
|
|
7391
|
+
}
|
|
7392
|
+
// -------------------------------------------------------------------------
|
|
7393
|
+
// Audio playback (system default speakers)
|
|
7394
|
+
// -------------------------------------------------------------------------
|
|
7395
|
+
async playWav(path) {
|
|
7396
|
+
this.killPlayback();
|
|
7397
|
+
const cmd = this.getPlayCommand(path);
|
|
7398
|
+
if (!cmd)
|
|
7399
|
+
return;
|
|
7400
|
+
return new Promise((resolve12) => {
|
|
7401
|
+
const child = nodeSpawn(cmd[0], cmd.slice(1), {
|
|
7402
|
+
stdio: "ignore",
|
|
7403
|
+
detached: false
|
|
7404
|
+
});
|
|
7405
|
+
this.currentPlayback = child;
|
|
7406
|
+
child.on("close", () => {
|
|
7407
|
+
if (this.currentPlayback === child)
|
|
7408
|
+
this.currentPlayback = null;
|
|
7409
|
+
resolve12();
|
|
7410
|
+
});
|
|
7411
|
+
child.on("error", () => {
|
|
7412
|
+
if (this.currentPlayback === child)
|
|
7413
|
+
this.currentPlayback = null;
|
|
7414
|
+
resolve12();
|
|
7415
|
+
});
|
|
7416
|
+
setTimeout(() => {
|
|
7417
|
+
this.killPlayback();
|
|
7418
|
+
resolve12();
|
|
7419
|
+
}, 15e3);
|
|
7420
|
+
});
|
|
7421
|
+
}
|
|
7422
|
+
getPlayCommand(path) {
|
|
7423
|
+
const os = platform();
|
|
7424
|
+
if (os === "darwin")
|
|
7425
|
+
return ["afplay", path];
|
|
7426
|
+
if (os === "win32") {
|
|
7427
|
+
return [
|
|
7428
|
+
"powershell",
|
|
7429
|
+
"-c",
|
|
7430
|
+
`(New-Object Media.SoundPlayer '${path}').PlaySync()`
|
|
7431
|
+
];
|
|
7432
|
+
}
|
|
7433
|
+
for (const player of ["paplay", "pw-play", "aplay"]) {
|
|
7434
|
+
try {
|
|
7435
|
+
execSync8(`which ${player}`, { stdio: "pipe" });
|
|
7436
|
+
return [player, path];
|
|
7437
|
+
} catch {
|
|
7438
|
+
}
|
|
7439
|
+
}
|
|
7440
|
+
return null;
|
|
7441
|
+
}
|
|
7442
|
+
killPlayback() {
|
|
7443
|
+
if (this.currentPlayback) {
|
|
7444
|
+
try {
|
|
7445
|
+
this.currentPlayback.kill("SIGTERM");
|
|
7446
|
+
} catch {
|
|
7447
|
+
}
|
|
7448
|
+
this.currentPlayback = null;
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
7451
|
+
// -------------------------------------------------------------------------
|
|
7452
|
+
// Setup: ONNX runtime installation
|
|
7453
|
+
// -------------------------------------------------------------------------
|
|
7454
|
+
async ensureRuntime() {
|
|
7455
|
+
if (this.ort)
|
|
7456
|
+
return;
|
|
7457
|
+
mkdirSync4(VOICE_DIR, { recursive: true });
|
|
7458
|
+
const pkgPath = join13(VOICE_DIR, "package.json");
|
|
7459
|
+
if (!existsSync8(pkgPath)) {
|
|
7460
|
+
writeFileSync4(pkgPath, JSON.stringify({
|
|
7461
|
+
name: "open-agents-voice",
|
|
7462
|
+
private: true,
|
|
7463
|
+
dependencies: { "onnxruntime-node": "^1.21.0" }
|
|
7464
|
+
}, null, 2));
|
|
7465
|
+
}
|
|
7466
|
+
const voiceRequire = createRequire(join13(VOICE_DIR, "index.js"));
|
|
7467
|
+
try {
|
|
7468
|
+
this.ort = voiceRequire("onnxruntime-node");
|
|
7469
|
+
} catch {
|
|
7470
|
+
renderInfo("Installing ONNX runtime for voice synthesis...");
|
|
7471
|
+
try {
|
|
7472
|
+
execSync8("npm install --no-audit --no-fund", {
|
|
7473
|
+
cwd: VOICE_DIR,
|
|
7474
|
+
stdio: "pipe",
|
|
7475
|
+
timeout: 12e4
|
|
7476
|
+
});
|
|
7477
|
+
this.ort = voiceRequire("onnxruntime-node");
|
|
7478
|
+
} catch (err) {
|
|
7479
|
+
throw new Error(`Failed to install onnxruntime-node. Try manually: cd ${VOICE_DIR} && npm install
|
|
7480
|
+
Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
try {
|
|
7484
|
+
execSync8("espeak-ng --version", { stdio: "pipe" });
|
|
7485
|
+
this.hasEspeak = true;
|
|
7486
|
+
} catch {
|
|
7487
|
+
this.hasEspeak = false;
|
|
7488
|
+
renderWarning("espeak-ng not found \u2014 using basic phonemization. For better voice quality: sudo apt install espeak-ng");
|
|
7489
|
+
}
|
|
7490
|
+
}
|
|
7491
|
+
// -------------------------------------------------------------------------
|
|
7492
|
+
// Setup: Model download
|
|
7493
|
+
// -------------------------------------------------------------------------
|
|
7494
|
+
async ensureModel(id) {
|
|
7495
|
+
const model = VOICE_MODELS[id];
|
|
7496
|
+
if (!model)
|
|
7497
|
+
throw new Error(`Unknown model: ${id}`);
|
|
7498
|
+
const dir = modelDir(id);
|
|
7499
|
+
const onnxPath = modelOnnxPath(id);
|
|
7500
|
+
const configPath = modelConfigPath(id);
|
|
7501
|
+
if (existsSync8(onnxPath) && existsSync8(configPath))
|
|
7502
|
+
return;
|
|
7503
|
+
mkdirSync4(dir, { recursive: true });
|
|
7504
|
+
if (!existsSync8(configPath)) {
|
|
7505
|
+
renderInfo(`Downloading ${model.label} voice config...`);
|
|
7506
|
+
const configResp = await fetch(model.configUrl);
|
|
7507
|
+
if (!configResp.ok)
|
|
7508
|
+
throw new Error(`Failed to download config: HTTP ${configResp.status}`);
|
|
7509
|
+
const configText = await configResp.text();
|
|
7510
|
+
writeFileSync4(configPath, configText);
|
|
7511
|
+
}
|
|
7512
|
+
if (!existsSync8(onnxPath)) {
|
|
7513
|
+
renderInfo(`Downloading ${model.label} voice model (this may take a minute)...`);
|
|
7514
|
+
const onnxResp = await fetch(model.onnxUrl);
|
|
7515
|
+
if (!onnxResp.ok)
|
|
7516
|
+
throw new Error(`Failed to download model: HTTP ${onnxResp.status}`);
|
|
7517
|
+
const reader = onnxResp.body?.getReader();
|
|
7518
|
+
if (!reader)
|
|
7519
|
+
throw new Error("No response body");
|
|
7520
|
+
const contentLength = parseInt(onnxResp.headers.get("content-length") || "0", 10);
|
|
7521
|
+
const chunks = [];
|
|
7522
|
+
let received = 0;
|
|
7523
|
+
while (true) {
|
|
7524
|
+
const { done, value } = await reader.read();
|
|
7525
|
+
if (done)
|
|
7526
|
+
break;
|
|
7527
|
+
chunks.push(value);
|
|
7528
|
+
received += value.length;
|
|
7529
|
+
if (contentLength > 0) {
|
|
7530
|
+
const pct = Math.round(received / contentLength * 100);
|
|
7531
|
+
process.stdout.write(`\r ${c2.dim(` ${pct}% (${formatBytes2(received)} / ${formatBytes2(contentLength)})`)}`);
|
|
7532
|
+
}
|
|
7533
|
+
}
|
|
7534
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
7535
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
7536
|
+
writeFileSync4(onnxPath, fullBuffer);
|
|
7537
|
+
renderInfo(`${model.label} model downloaded (${formatBytes2(fullBuffer.length)}).`);
|
|
7538
|
+
}
|
|
7539
|
+
}
|
|
7540
|
+
// -------------------------------------------------------------------------
|
|
7541
|
+
// Load ONNX session
|
|
7542
|
+
// -------------------------------------------------------------------------
|
|
7543
|
+
async loadSession() {
|
|
7544
|
+
if (!this.ort)
|
|
7545
|
+
throw new Error("ONNX runtime not loaded");
|
|
7546
|
+
const onnxPath = modelOnnxPath(this.modelId);
|
|
7547
|
+
const configPath = modelConfigPath(this.modelId);
|
|
7548
|
+
if (!existsSync8(onnxPath) || !existsSync8(configPath)) {
|
|
7549
|
+
throw new Error(`Model files not found for ${this.modelId}`);
|
|
7550
|
+
}
|
|
7551
|
+
this.config = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
7552
|
+
renderInfo("Loading voice model...");
|
|
7553
|
+
this.session = await this.ort.InferenceSession.create(onnxPath, {
|
|
7554
|
+
executionProviders: ["cpu"],
|
|
7555
|
+
graphOptimizationLevel: "all"
|
|
7556
|
+
});
|
|
7557
|
+
renderInfo("Voice model loaded.");
|
|
7558
|
+
}
|
|
7559
|
+
};
|
|
7560
|
+
}
|
|
7561
|
+
});
|
|
7562
|
+
|
|
7006
7563
|
// packages/cli/dist/tui/interactive.js
|
|
7007
7564
|
import * as readline2 from "node:readline";
|
|
7008
7565
|
import { cwd } from "node:process";
|
|
@@ -7059,7 +7616,7 @@ function buildTools(repoRoot) {
|
|
|
7059
7616
|
];
|
|
7060
7617
|
return [...executionTools.map(adaptTool), createTaskCompleteTool()];
|
|
7061
7618
|
}
|
|
7062
|
-
async function runTask(task, config, repoRoot) {
|
|
7619
|
+
async function runTask(task, config, repoRoot, voice) {
|
|
7063
7620
|
const backend = new OllamaAgenticBackend(config.backendUrl.replace(/\/$/, ""), config.model);
|
|
7064
7621
|
const runner = new AgenticRunner(backend, {
|
|
7065
7622
|
maxTurns: 30,
|
|
@@ -7074,9 +7631,16 @@ async function runTask(task, config, repoRoot) {
|
|
|
7074
7631
|
switch (event.type) {
|
|
7075
7632
|
case "tool_call":
|
|
7076
7633
|
renderToolCallStart(event.toolName ?? "unknown", event.toolArgs ?? {});
|
|
7634
|
+
if (voice?.enabled) {
|
|
7635
|
+
const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {});
|
|
7636
|
+
voice.speak(desc);
|
|
7637
|
+
}
|
|
7077
7638
|
break;
|
|
7078
7639
|
case "tool_result":
|
|
7079
7640
|
renderToolResult(event.toolName ?? "unknown", event.success ?? false, event.content ?? "");
|
|
7641
|
+
if (voice?.enabled && !(event.success ?? true)) {
|
|
7642
|
+
voice.speak(describeToolResult(event.toolName ?? "unknown", false));
|
|
7643
|
+
}
|
|
7080
7644
|
break;
|
|
7081
7645
|
case "model_response":
|
|
7082
7646
|
if (config.verbose && event.content) {
|
|
@@ -7124,6 +7688,7 @@ async function startInteractive(config, repoPath) {
|
|
|
7124
7688
|
process.exit(1);
|
|
7125
7689
|
}
|
|
7126
7690
|
renderHeader(config.model);
|
|
7691
|
+
const voiceEngine = new VoiceEngine();
|
|
7127
7692
|
let currentConfig = { ...config };
|
|
7128
7693
|
const rl = readline2.createInterface({
|
|
7129
7694
|
input: process.stdin,
|
|
@@ -7155,7 +7720,14 @@ async function startInteractive(config, repoPath) {
|
|
|
7155
7720
|
renderCompactHeader(currentConfig.model);
|
|
7156
7721
|
},
|
|
7157
7722
|
exit() {
|
|
7723
|
+
voiceEngine.dispose();
|
|
7158
7724
|
rl.close();
|
|
7725
|
+
},
|
|
7726
|
+
async voiceToggle() {
|
|
7727
|
+
return voiceEngine.toggle();
|
|
7728
|
+
},
|
|
7729
|
+
async voiceSetModel(id) {
|
|
7730
|
+
return voiceEngine.setModel(id);
|
|
7159
7731
|
}
|
|
7160
7732
|
};
|
|
7161
7733
|
rl.prompt();
|
|
@@ -7180,7 +7752,7 @@ ${c2.dim("Goodbye!")}
|
|
|
7180
7752
|
}
|
|
7181
7753
|
renderUserMessage(input);
|
|
7182
7754
|
try {
|
|
7183
|
-
await runTask(input, currentConfig, repoRoot);
|
|
7755
|
+
await runTask(input, currentConfig, repoRoot, voiceEngine);
|
|
7184
7756
|
} catch (err) {
|
|
7185
7757
|
renderError(err instanceof Error ? err.message : String(err));
|
|
7186
7758
|
}
|
|
@@ -7239,6 +7811,7 @@ var init_interactive = __esm({
|
|
|
7239
7811
|
init_commands();
|
|
7240
7812
|
init_setup();
|
|
7241
7813
|
init_render();
|
|
7814
|
+
init_voice();
|
|
7242
7815
|
}
|
|
7243
7816
|
});
|
|
7244
7817
|
|
|
@@ -7273,7 +7846,7 @@ import { glob } from "glob";
|
|
|
7273
7846
|
import ignore from "ignore";
|
|
7274
7847
|
import { readFile as readFile8, stat as stat2 } from "node:fs/promises";
|
|
7275
7848
|
import { createHash } from "node:crypto";
|
|
7276
|
-
import { join as
|
|
7849
|
+
import { join as join14, relative as relative2, extname as extname3, basename } from "node:path";
|
|
7277
7850
|
var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
|
|
7278
7851
|
var init_codebase_indexer = __esm({
|
|
7279
7852
|
"packages/indexer/dist/codebase-indexer.js"() {
|
|
@@ -7317,7 +7890,7 @@ var init_codebase_indexer = __esm({
|
|
|
7317
7890
|
const ig = ignore.default();
|
|
7318
7891
|
if (this.config.respectGitignore) {
|
|
7319
7892
|
try {
|
|
7320
|
-
const gitignoreContent = await readFile8(
|
|
7893
|
+
const gitignoreContent = await readFile8(join14(this.config.rootDir, ".gitignore"), "utf-8");
|
|
7321
7894
|
ig.add(gitignoreContent);
|
|
7322
7895
|
} catch {
|
|
7323
7896
|
}
|
|
@@ -7332,7 +7905,7 @@ var init_codebase_indexer = __esm({
|
|
|
7332
7905
|
for (const relativePath of files) {
|
|
7333
7906
|
if (ig.ignores(relativePath))
|
|
7334
7907
|
continue;
|
|
7335
|
-
const fullPath =
|
|
7908
|
+
const fullPath = join14(this.config.rootDir, relativePath);
|
|
7336
7909
|
try {
|
|
7337
7910
|
const fileStat = await stat2(fullPath);
|
|
7338
7911
|
if (fileStat.size > this.config.maxFileSize)
|
|
@@ -7378,7 +7951,7 @@ var init_codebase_indexer = __esm({
|
|
|
7378
7951
|
if (!child) {
|
|
7379
7952
|
child = {
|
|
7380
7953
|
name: part,
|
|
7381
|
-
path:
|
|
7954
|
+
path: join14(current.path, part),
|
|
7382
7955
|
type: "directory",
|
|
7383
7956
|
children: []
|
|
7384
7957
|
};
|
|
@@ -7453,13 +8026,13 @@ __export(index_repo_exports, {
|
|
|
7453
8026
|
indexRepoCommand: () => indexRepoCommand
|
|
7454
8027
|
});
|
|
7455
8028
|
import { resolve as resolve11 } from "node:path";
|
|
7456
|
-
import { existsSync as
|
|
8029
|
+
import { existsSync as existsSync9, statSync as statSync4 } from "node:fs";
|
|
7457
8030
|
import { cwd as cwd2 } from "node:process";
|
|
7458
8031
|
async function indexRepoCommand(opts, _config) {
|
|
7459
8032
|
const repoRoot = resolve11(opts.repoPath ?? cwd2());
|
|
7460
8033
|
printHeader("Index Repository");
|
|
7461
8034
|
printInfo(`Indexing: ${repoRoot}`);
|
|
7462
|
-
if (!
|
|
8035
|
+
if (!existsSync9(repoRoot)) {
|
|
7463
8036
|
printError(`Path does not exist: ${repoRoot}`);
|
|
7464
8037
|
process.exit(1);
|
|
7465
8038
|
}
|
|
@@ -7705,8 +8278,8 @@ var config_exports = {};
|
|
|
7705
8278
|
__export(config_exports, {
|
|
7706
8279
|
configCommand: () => configCommand
|
|
7707
8280
|
});
|
|
7708
|
-
import { join as
|
|
7709
|
-
import { homedir as
|
|
8281
|
+
import { join as join15 } from "node:path";
|
|
8282
|
+
import { homedir as homedir5 } from "node:os";
|
|
7710
8283
|
async function configCommand(opts, config) {
|
|
7711
8284
|
if (opts.subCommand === "set") {
|
|
7712
8285
|
return handleSet(opts, config);
|
|
@@ -7728,7 +8301,7 @@ function handleShow(opts, config) {
|
|
|
7728
8301
|
printKeyValue("verbose", String(config.verbose), 2);
|
|
7729
8302
|
printKeyValue("dbPath", config.dbPath, 2);
|
|
7730
8303
|
printSection("Config File");
|
|
7731
|
-
printInfo(`~/.open-agents/config.json (${
|
|
8304
|
+
printInfo(`~/.open-agents/config.json (${join15(homedir5(), ".open-agents", "config.json")})`);
|
|
7732
8305
|
printSection("Environment Variables");
|
|
7733
8306
|
printInfo("OPEN_AGENTS_BACKEND_URL \u2014 override backendUrl");
|
|
7734
8307
|
printInfo("OPEN_AGENTS_MODEL \u2014 override model");
|
|
@@ -7802,7 +8375,7 @@ var serve_exports = {};
|
|
|
7802
8375
|
__export(serve_exports, {
|
|
7803
8376
|
serveCommand: () => serveCommand
|
|
7804
8377
|
});
|
|
7805
|
-
import { spawn } from "node:child_process";
|
|
8378
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7806
8379
|
async function serveCommand(opts, config) {
|
|
7807
8380
|
const backendType = config.backendType;
|
|
7808
8381
|
if (backendType === "ollama") {
|
|
@@ -7895,7 +8468,7 @@ async function serveVllm(opts, config) {
|
|
|
7895
8468
|
}
|
|
7896
8469
|
async function runVllmServer(args, verbose) {
|
|
7897
8470
|
return new Promise((resolve12, reject) => {
|
|
7898
|
-
const child =
|
|
8471
|
+
const child = spawn2("python", args, {
|
|
7899
8472
|
stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
7900
8473
|
env: { ...process.env }
|
|
7901
8474
|
});
|
|
@@ -7959,9 +8532,9 @@ var eval_exports = {};
|
|
|
7959
8532
|
__export(eval_exports, {
|
|
7960
8533
|
evalCommand: () => evalCommand
|
|
7961
8534
|
});
|
|
7962
|
-
import { tmpdir } from "node:os";
|
|
7963
|
-
import { mkdirSync as
|
|
7964
|
-
import { join as
|
|
8535
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
8536
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8537
|
+
import { join as join16 } from "node:path";
|
|
7965
8538
|
async function evalCommand(opts, config) {
|
|
7966
8539
|
const suiteName = opts.suite ?? "basic";
|
|
7967
8540
|
const suite = SUITES[suiteName];
|
|
@@ -8082,9 +8655,9 @@ async function evalCommand(opts, config) {
|
|
|
8082
8655
|
process.exit(failed > 0 ? 1 : 0);
|
|
8083
8656
|
}
|
|
8084
8657
|
function createTempEvalRepo() {
|
|
8085
|
-
const dir =
|
|
8086
|
-
|
|
8087
|
-
|
|
8658
|
+
const dir = join16(tmpdir2(), `open-agents-eval-${Date.now()}`);
|
|
8659
|
+
mkdirSync5(dir, { recursive: true });
|
|
8660
|
+
writeFileSync5(join16(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
|
|
8088
8661
|
return dir;
|
|
8089
8662
|
}
|
|
8090
8663
|
var BASIC_SUITE, FULL_SUITE, SUITES;
|
|
@@ -8142,9 +8715,9 @@ init_config();
|
|
|
8142
8715
|
init_output();
|
|
8143
8716
|
init_updater();
|
|
8144
8717
|
import { parseArgs as nodeParseArgs2 } from "node:util";
|
|
8145
|
-
import { createRequire } from "node:module";
|
|
8718
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
8146
8719
|
import { fileURLToPath } from "node:url";
|
|
8147
|
-
import { dirname as dirname3, join as
|
|
8720
|
+
import { dirname as dirname3, join as join17 } from "node:path";
|
|
8148
8721
|
|
|
8149
8722
|
// packages/cli/dist/cli.js
|
|
8150
8723
|
import { createInterface } from "node:readline";
|
|
@@ -8250,8 +8823,8 @@ init_spinner();
|
|
|
8250
8823
|
init_output();
|
|
8251
8824
|
function getVersion() {
|
|
8252
8825
|
try {
|
|
8253
|
-
const require2 =
|
|
8254
|
-
const pkgPath =
|
|
8826
|
+
const require2 = createRequire2(import.meta.url);
|
|
8827
|
+
const pkgPath = join17(dirname3(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
8255
8828
|
const pkg = require2(pkgPath);
|
|
8256
8829
|
return pkg.version;
|
|
8257
8830
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-agents-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AI coding agent powered by open-source models (Ollama/vLLM) — Claude Code-style TUI with agentic tool-calling loop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
"tool-calling",
|
|
26
26
|
"agentic",
|
|
27
27
|
"code-generation",
|
|
28
|
-
"developer-tools"
|
|
28
|
+
"developer-tools",
|
|
29
|
+
"tts",
|
|
30
|
+
"voice",
|
|
31
|
+
"onnx"
|
|
29
32
|
],
|
|
30
33
|
"author": "robit-man",
|
|
31
34
|
"license": "MIT",
|