open-agents-ai 0.13.5 → 0.14.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.
Files changed (2) hide show
  1. package/dist/index.js +1149 -237
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1170,7 +1170,7 @@ var init_shell = __esm({
1170
1170
  const timeout = args["timeout"] ?? this.defaultTimeout;
1171
1171
  const stdinInput = args["stdin"];
1172
1172
  const start = performance.now();
1173
- return new Promise((resolve15) => {
1173
+ return new Promise((resolve16) => {
1174
1174
  const child = spawn("bash", ["-c", command], {
1175
1175
  cwd: this.workingDir,
1176
1176
  env: {
@@ -1223,7 +1223,7 @@ var init_shell = __esm({
1223
1223
  const combined = stdout + stderr;
1224
1224
  const looksInteractive = /\? .+[›>]|y\/n|yes\/no|\(Y\/n\)|\[y\/N\]/i.test(combined);
1225
1225
  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." : "";
1226
- resolve15({
1226
+ resolve16({
1227
1227
  success: false,
1228
1228
  output: stdout,
1229
1229
  error: `Command timed out after ${timeout}ms.${hint}`,
@@ -1232,7 +1232,7 @@ var init_shell = __esm({
1232
1232
  return;
1233
1233
  }
1234
1234
  const success = code === 0;
1235
- resolve15({
1235
+ resolve16({
1236
1236
  success,
1237
1237
  output: stdout + (stderr && success ? `
1238
1238
  STDERR:
@@ -1243,7 +1243,7 @@ ${stderr}` : ""),
1243
1243
  });
1244
1244
  child.on("error", (err) => {
1245
1245
  clearTimeout(timer);
1246
- resolve15({
1246
+ resolve16({
1247
1247
  success: false,
1248
1248
  output: stdout,
1249
1249
  error: err.message,
@@ -4031,8 +4031,8 @@ function deleteCustomToolDefinition(name, scope, repoRoot) {
4031
4031
  const dir = scope === "project" && repoRoot ? projectToolsDir(repoRoot) : globalToolsDir();
4032
4032
  const filePath = join11(dir, `${name}.json`);
4033
4033
  if (existsSync8(filePath)) {
4034
- const { unlinkSync: unlinkSync3 } = __require("node:fs");
4035
- unlinkSync3(filePath);
4034
+ const { unlinkSync: unlinkSync4 } = __require("node:fs");
4035
+ unlinkSync4(filePath);
4036
4036
  return true;
4037
4037
  }
4038
4038
  return false;
@@ -4150,7 +4150,7 @@ var init_custom_tool = __esm({
4150
4150
  }
4151
4151
  /** Execute a single shell command and return output */
4152
4152
  runCommand(command) {
4153
- return new Promise((resolve15) => {
4153
+ return new Promise((resolve16) => {
4154
4154
  const child = spawn3("bash", ["-c", command], {
4155
4155
  cwd: this.workingDir,
4156
4156
  env: { ...process.env, CI: "true", NO_COLOR: "1" },
@@ -4175,11 +4175,11 @@ var init_custom_tool = __esm({
4175
4175
  child.kill("SIGTERM");
4176
4176
  } catch {
4177
4177
  }
4178
- resolve15({ success: false, output: stdout, error: "Command timed out after 60s" });
4178
+ resolve16({ success: false, output: stdout, error: "Command timed out after 60s" });
4179
4179
  }, 6e4);
4180
4180
  child.on("close", (code) => {
4181
4181
  clearTimeout(timer);
4182
- resolve15({
4182
+ resolve16({
4183
4183
  success: code === 0,
4184
4184
  output: stdout + (stderr && code === 0 ? `
4185
4185
  STDERR:
@@ -4189,7 +4189,7 @@ ${stderr}` : ""),
4189
4189
  });
4190
4190
  child.on("error", (err) => {
4191
4191
  clearTimeout(timer);
4192
- resolve15({ success: false, output: stdout, error: err.message });
4192
+ resolve16({ success: false, output: stdout, error: err.message });
4193
4193
  });
4194
4194
  });
4195
4195
  }
@@ -4840,6 +4840,300 @@ ${content}`,
4840
4840
  }
4841
4841
  });
4842
4842
 
4843
+ // packages/execution/dist/tools/transcribe-tool.js
4844
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, readFileSync as readFileSync9, unlinkSync } from "node:fs";
4845
+ import { join as join13, basename as basename3, extname as extname3, resolve as resolve12 } from "node:path";
4846
+ import { homedir as homedir5 } from "node:os";
4847
+ import { execSync as execSync9, spawn as spawn4 } from "node:child_process";
4848
+ function isTranscribable(path) {
4849
+ const ext = extname3(path).toLowerCase();
4850
+ return AUDIO_EXTS.has(ext) || VIDEO_EXTS.has(ext);
4851
+ }
4852
+ async function loadTranscribeCli() {
4853
+ if (_tcChecked)
4854
+ return _tcModule;
4855
+ _tcChecked = true;
4856
+ try {
4857
+ const globalRoot = execSync9("npm root -g", {
4858
+ encoding: "utf-8",
4859
+ timeout: 5e3,
4860
+ stdio: ["pipe", "pipe", "pipe"]
4861
+ }).trim();
4862
+ const tcPath = join13(globalRoot, "transcribe-cli");
4863
+ if (existsSync10(join13(tcPath, "dist", "index.js"))) {
4864
+ const { createRequire: createRequire4 } = await import("node:module");
4865
+ const req = createRequire4(import.meta.url);
4866
+ _tcModule = req(join13(tcPath, "dist", "index.js"));
4867
+ return _tcModule;
4868
+ }
4869
+ } catch {
4870
+ }
4871
+ const nvmBase = join13(homedir5(), ".nvm", "versions", "node");
4872
+ if (existsSync10(nvmBase)) {
4873
+ try {
4874
+ const { readdirSync: readdirSync9 } = await import("node:fs");
4875
+ for (const ver of readdirSync9(nvmBase)) {
4876
+ const tcPath = join13(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
4877
+ if (existsSync10(join13(tcPath, "dist", "index.js"))) {
4878
+ const { createRequire: createRequire4 } = await import("node:module");
4879
+ const req = createRequire4(import.meta.url);
4880
+ _tcModule = req(join13(tcPath, "dist", "index.js"));
4881
+ return _tcModule;
4882
+ }
4883
+ }
4884
+ } catch {
4885
+ }
4886
+ }
4887
+ return null;
4888
+ }
4889
+ function formatTime(seconds) {
4890
+ const m = Math.floor(seconds / 60);
4891
+ const s = Math.floor(seconds % 60);
4892
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
4893
+ }
4894
+ var AUDIO_EXTS, VIDEO_EXTS, _tcModule, _tcChecked, TranscribeFileTool, TranscribeUrlTool;
4895
+ var init_transcribe_tool = __esm({
4896
+ "packages/execution/dist/tools/transcribe-tool.js"() {
4897
+ "use strict";
4898
+ AUDIO_EXTS = /* @__PURE__ */ new Set([
4899
+ ".mp3",
4900
+ ".wav",
4901
+ ".flac",
4902
+ ".aac",
4903
+ ".m4a",
4904
+ ".ogg",
4905
+ ".wma",
4906
+ ".opus"
4907
+ ]);
4908
+ VIDEO_EXTS = /* @__PURE__ */ new Set([
4909
+ ".mp4",
4910
+ ".mkv",
4911
+ ".avi",
4912
+ ".mov",
4913
+ ".webm",
4914
+ ".flv",
4915
+ ".wmv",
4916
+ ".m4v",
4917
+ ".ts"
4918
+ ]);
4919
+ _tcModule = null;
4920
+ _tcChecked = false;
4921
+ TranscribeFileTool = class {
4922
+ name = "transcribe_file";
4923
+ description = "Transcribe a local audio or video file to text using Whisper (faster-whisper). Supports MP3, WAV, FLAC, AAC, M4A, OGG, MP4, MKV, AVI, MOV, WebM. Returns the full transcription text with optional speaker diarization. Transcription is 100% local \u2014 no API keys needed.";
4924
+ parameters = {
4925
+ type: "object",
4926
+ properties: {
4927
+ path: {
4928
+ type: "string",
4929
+ description: "Path to the audio or video file to transcribe"
4930
+ },
4931
+ model: {
4932
+ type: "string",
4933
+ description: "Whisper model size: tiny, base, small, medium, large-v3 (default: base)",
4934
+ enum: ["tiny", "base", "small", "medium", "large-v3"]
4935
+ },
4936
+ diarize: {
4937
+ type: "boolean",
4938
+ description: "Enable speaker diarization (identify who said what). Default: false"
4939
+ }
4940
+ },
4941
+ required: ["path"]
4942
+ };
4943
+ workingDir;
4944
+ constructor(workingDir) {
4945
+ this.workingDir = workingDir;
4946
+ }
4947
+ async execute(args) {
4948
+ const start = performance.now();
4949
+ const filePath = resolve12(this.workingDir, String(args["path"] ?? ""));
4950
+ const model = String(args["model"] ?? "base");
4951
+ const diarize = Boolean(args["diarize"] ?? false);
4952
+ if (!existsSync10(filePath)) {
4953
+ return {
4954
+ success: false,
4955
+ output: "",
4956
+ error: `File not found: ${filePath}`,
4957
+ durationMs: performance.now() - start
4958
+ };
4959
+ }
4960
+ if (!isTranscribable(filePath)) {
4961
+ return {
4962
+ success: false,
4963
+ output: "",
4964
+ error: `Unsupported file type: ${extname3(filePath)}. Supported: ${[...AUDIO_EXTS, ...VIDEO_EXTS].join(", ")}`,
4965
+ durationMs: performance.now() - start
4966
+ };
4967
+ }
4968
+ const tc = await loadTranscribeCli();
4969
+ if (!tc) {
4970
+ return this.execViaCli(filePath, model, diarize, start);
4971
+ }
4972
+ try {
4973
+ const result = await tc.transcribe(filePath, {
4974
+ model,
4975
+ format: "json",
4976
+ diarize,
4977
+ wordTimestamps: false
4978
+ });
4979
+ const transcriptDir = join13(this.workingDir, ".oa", "transcripts");
4980
+ mkdirSync4(transcriptDir, { recursive: true });
4981
+ const outFile = join13(transcriptDir, `${basename3(filePath)}.txt`);
4982
+ writeFileSync4(outFile, result.text, "utf-8");
4983
+ const lines = [
4984
+ `Transcription of: ${basename3(filePath)}`,
4985
+ `Model: ${model} | Language: ${result.language} | Duration: ${result.duration ? `${result.duration.toFixed(1)}s` : "unknown"}`,
4986
+ `Words: ${result.wordCount} | Saved to: ${outFile}`,
4987
+ ""
4988
+ ];
4989
+ if (result.speakers.length > 1) {
4990
+ lines.push(`Speakers: ${result.speakers.join(", ")}`);
4991
+ lines.push("");
4992
+ }
4993
+ if (diarize && result.segments.length > 0) {
4994
+ for (const seg of result.segments) {
4995
+ const ts = `[${formatTime(seg.start)} \u2192 ${formatTime(seg.end)}]`;
4996
+ const speaker = seg.speaker ? `${seg.speaker}: ` : "";
4997
+ lines.push(`${ts} ${speaker}${seg.text}`);
4998
+ }
4999
+ } else {
5000
+ lines.push(result.text);
5001
+ }
5002
+ return {
5003
+ success: true,
5004
+ output: lines.join("\n"),
5005
+ durationMs: performance.now() - start
5006
+ };
5007
+ } catch (err) {
5008
+ return {
5009
+ success: false,
5010
+ output: "",
5011
+ error: `Transcription failed: ${err instanceof Error ? err.message : String(err)}`,
5012
+ durationMs: performance.now() - start
5013
+ };
5014
+ }
5015
+ }
5016
+ /** Fallback: invoke transcribe-cli as a subprocess. */
5017
+ async execViaCli(filePath, model, diarize, start) {
5018
+ try {
5019
+ const args = [filePath, "-m", model, "-f", "txt"];
5020
+ if (diarize)
5021
+ args.push("--diarize");
5022
+ const output = execSync9(`transcribe-cli ${args.join(" ")}`, {
5023
+ encoding: "utf-8",
5024
+ timeout: 3e5,
5025
+ // 5 min max
5026
+ cwd: this.workingDir,
5027
+ stdio: ["pipe", "pipe", "pipe"]
5028
+ });
5029
+ return {
5030
+ success: true,
5031
+ output: output.trim() || "(empty transcription)",
5032
+ durationMs: performance.now() - start
5033
+ };
5034
+ } catch (err) {
5035
+ const stderr = err.stderr || "";
5036
+ return {
5037
+ success: false,
5038
+ output: err.stdout || "",
5039
+ error: `transcribe-cli failed: ${stderr || err.message}`,
5040
+ durationMs: performance.now() - start
5041
+ };
5042
+ }
5043
+ }
5044
+ };
5045
+ TranscribeUrlTool = class {
5046
+ name = "transcribe_url";
5047
+ description = "Download an audio or video file from a URL and transcribe it to text. Supports direct links to MP3, WAV, MP4, and other media files. The file is downloaded to a temp location, transcribed locally, then cleaned up.";
5048
+ parameters = {
5049
+ type: "object",
5050
+ properties: {
5051
+ url: {
5052
+ type: "string",
5053
+ description: "URL of the audio or video file to download and transcribe"
5054
+ },
5055
+ model: {
5056
+ type: "string",
5057
+ description: "Whisper model size: tiny, base, small, medium, large-v3 (default: base)",
5058
+ enum: ["tiny", "base", "small", "medium", "large-v3"]
5059
+ }
5060
+ },
5061
+ required: ["url"]
5062
+ };
5063
+ workingDir;
5064
+ constructor(workingDir) {
5065
+ this.workingDir = workingDir;
5066
+ }
5067
+ async execute(args) {
5068
+ const start = performance.now();
5069
+ const url = String(args["url"] ?? "");
5070
+ const model = String(args["model"] ?? "base");
5071
+ if (!url) {
5072
+ return {
5073
+ success: false,
5074
+ output: "",
5075
+ error: "URL is required.",
5076
+ durationMs: performance.now() - start
5077
+ };
5078
+ }
5079
+ const tmpDir = join13(this.workingDir, ".oa", "tmp");
5080
+ mkdirSync4(tmpDir, { recursive: true });
5081
+ const urlPath = new URL(url).pathname;
5082
+ let ext = extname3(urlPath).toLowerCase();
5083
+ if (!ext || !AUDIO_EXTS.has(ext) && !VIDEO_EXTS.has(ext)) {
5084
+ ext = ".mp3";
5085
+ }
5086
+ const tmpFile = join13(tmpDir, `download-${Date.now()}${ext}`);
5087
+ try {
5088
+ try {
5089
+ execSync9(`curl -sL -o "${tmpFile}" "${url}"`, {
5090
+ timeout: 12e4,
5091
+ stdio: ["pipe", "pipe", "pipe"]
5092
+ });
5093
+ } catch {
5094
+ execSync9(`wget -q -O "${tmpFile}" "${url}"`, {
5095
+ timeout: 12e4,
5096
+ stdio: ["pipe", "pipe", "pipe"]
5097
+ });
5098
+ }
5099
+ if (!existsSync10(tmpFile)) {
5100
+ return {
5101
+ success: false,
5102
+ output: "",
5103
+ error: "Download failed \u2014 file not created.",
5104
+ durationMs: performance.now() - start
5105
+ };
5106
+ }
5107
+ const fileTool = new TranscribeFileTool(this.workingDir);
5108
+ const result = await fileTool.execute({ path: tmpFile, model });
5109
+ try {
5110
+ unlinkSync(tmpFile);
5111
+ } catch {
5112
+ }
5113
+ return {
5114
+ success: result.success,
5115
+ output: `Source: ${url}
5116
+ ${result.output}`,
5117
+ error: result.error,
5118
+ durationMs: performance.now() - start
5119
+ };
5120
+ } catch (err) {
5121
+ try {
5122
+ unlinkSync(tmpFile);
5123
+ } catch {
5124
+ }
5125
+ return {
5126
+ success: false,
5127
+ output: "",
5128
+ error: `Failed to download/transcribe: ${err instanceof Error ? err.message : String(err)}`,
5129
+ durationMs: performance.now() - start
5130
+ };
5131
+ }
5132
+ }
5133
+ };
5134
+ }
5135
+ });
5136
+
4843
5137
  // packages/execution/dist/shellRunner.js
4844
5138
  var init_shellRunner = __esm({
4845
5139
  "packages/execution/dist/shellRunner.js"() {
@@ -4943,6 +5237,7 @@ var init_dist2 = __esm({
4943
5237
  init_custom_tool();
4944
5238
  init_tool_creator();
4945
5239
  init_skill_tools();
5240
+ init_transcribe_tool();
4946
5241
  init_shellRunner();
4947
5242
  init_gitWorktree();
4948
5243
  init_patchApplier();
@@ -6074,7 +6369,7 @@ var init_code_retriever = __esm({
6074
6369
  import { execFile as execFile4 } from "node:child_process";
6075
6370
  import { promisify as promisify4 } from "node:util";
6076
6371
  import { readFile as readFile7, readdir, stat } from "node:fs/promises";
6077
- import { join as join13, extname as extname3 } from "node:path";
6372
+ import { join as join14, extname as extname4 } from "node:path";
6078
6373
  async function searchByPath(pathPattern, options) {
6079
6374
  const allFiles = await collectFiles(options.rootDir, options.includeGlobs ?? DEFAULT_INCLUDE_GLOBS, options.excludeGlobs ?? DEFAULT_EXCLUDE_GLOBS);
6080
6375
  const pattern = options.caseInsensitive ? pathPattern.toLowerCase() : pathPattern;
@@ -6216,11 +6511,11 @@ async function walkForFiles(rootDir, dir, excludeGlobs, results) {
6216
6511
  continue;
6217
6512
  if (excludeGlobs.some((g) => entry.name === g || matchesGlob(entry.name, g)))
6218
6513
  continue;
6219
- const absPath = join13(dir, entry.name);
6514
+ const absPath = join14(dir, entry.name);
6220
6515
  if (entry.isDirectory()) {
6221
6516
  await walkForFiles(rootDir, absPath, excludeGlobs, results);
6222
6517
  } else if (entry.isFile()) {
6223
- const ext = extname3(entry.name);
6518
+ const ext = extname4(entry.name);
6224
6519
  if (!ALL_CODE_EXTS.has(ext))
6225
6520
  continue;
6226
6521
  const relativePath = absPath.startsWith(rootDir) ? absPath.slice(rootDir.length).replace(/^\//, "") : absPath;
@@ -6391,7 +6686,7 @@ var init_graphExpand = __esm({
6391
6686
 
6392
6687
  // packages/retrieval/dist/snippetPacker.js
6393
6688
  import { readFile as readFile8 } from "node:fs/promises";
6394
- import { join as join14 } from "node:path";
6689
+ import { join as join15 } from "node:path";
6395
6690
  async function packSnippets(requests, opts = {}) {
6396
6691
  const maxTokens = opts.maxTokens ?? DEFAULT_MAX_TOKENS;
6397
6692
  const contextLines = opts.contextLines ?? DEFAULT_CONTEXT_LINES;
@@ -6417,7 +6712,7 @@ async function packSnippets(requests, opts = {}) {
6417
6712
  return { packed, dropped, totalTokens };
6418
6713
  }
6419
6714
  async function extractSnippet(req, repoRoot, contextLines = DEFAULT_CONTEXT_LINES) {
6420
- const absPath = req.filePath.startsWith("/") ? req.filePath : join14(repoRoot, req.filePath);
6715
+ const absPath = req.filePath.startsWith("/") ? req.filePath : join15(repoRoot, req.filePath);
6421
6716
  let content;
6422
6717
  try {
6423
6718
  content = await readFile8(absPath, "utf-8");
@@ -8726,6 +9021,425 @@ var init_dist5 = __esm({
8726
9021
  }
8727
9022
  });
8728
9023
 
9024
+ // packages/cli/dist/tui/listen.js
9025
+ import { spawn as spawn5, execSync as execSync10 } from "node:child_process";
9026
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
9027
+ import { join as join16 } from "node:path";
9028
+ import { homedir as homedir6 } from "node:os";
9029
+ import { EventEmitter } from "node:events";
9030
+ function isAudioPath(path) {
9031
+ const ext = path.toLowerCase().split(".").pop();
9032
+ return ext ? AUDIO_EXTENSIONS.has(`.${ext}`) : false;
9033
+ }
9034
+ function isVideoPath(path) {
9035
+ const ext = path.toLowerCase().split(".").pop();
9036
+ return ext ? VIDEO_EXTENSIONS.has(`.${ext}`) : false;
9037
+ }
9038
+ function isTranscribablePath(path) {
9039
+ return isAudioPath(path) || isVideoPath(path);
9040
+ }
9041
+ function findMicCaptureCommand() {
9042
+ const platform3 = process.platform;
9043
+ if (platform3 === "linux") {
9044
+ try {
9045
+ execSync10("which arecord", { stdio: "pipe" });
9046
+ return {
9047
+ cmd: "arecord",
9048
+ args: ["-f", "S16_LE", "-r", "16000", "-c", "1", "-t", "raw", "-q", "-"]
9049
+ };
9050
+ } catch {
9051
+ }
9052
+ }
9053
+ if (platform3 === "darwin") {
9054
+ try {
9055
+ execSync10("which sox", { stdio: "pipe" });
9056
+ return {
9057
+ cmd: "sox",
9058
+ args: ["-d", "-t", "raw", "-r", "16000", "-c", "1", "-b", "16", "-e", "signed-integer", "-"]
9059
+ };
9060
+ } catch {
9061
+ }
9062
+ }
9063
+ try {
9064
+ execSync10("which ffmpeg", { stdio: "pipe" });
9065
+ if (platform3 === "linux") {
9066
+ return {
9067
+ cmd: "ffmpeg",
9068
+ args: [
9069
+ "-f",
9070
+ "pulse",
9071
+ "-i",
9072
+ "default",
9073
+ "-ar",
9074
+ "16000",
9075
+ "-ac",
9076
+ "1",
9077
+ "-f",
9078
+ "s16le",
9079
+ "-loglevel",
9080
+ "quiet",
9081
+ "pipe:1"
9082
+ ]
9083
+ };
9084
+ } else if (platform3 === "darwin") {
9085
+ return {
9086
+ cmd: "ffmpeg",
9087
+ args: [
9088
+ "-f",
9089
+ "avfoundation",
9090
+ "-i",
9091
+ ":0",
9092
+ "-ar",
9093
+ "16000",
9094
+ "-ac",
9095
+ "1",
9096
+ "-f",
9097
+ "s16le",
9098
+ "-loglevel",
9099
+ "quiet",
9100
+ "pipe:1"
9101
+ ]
9102
+ };
9103
+ }
9104
+ } catch {
9105
+ }
9106
+ return null;
9107
+ }
9108
+ function getListenEngine(config) {
9109
+ if (!_engine) {
9110
+ _engine = new ListenEngine(config);
9111
+ }
9112
+ return _engine;
9113
+ }
9114
+ var AUDIO_EXTENSIONS, VIDEO_EXTENSIONS, ListenEngine, _engine;
9115
+ var init_listen = __esm({
9116
+ "packages/cli/dist/tui/listen.js"() {
9117
+ "use strict";
9118
+ AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
9119
+ ".mp3",
9120
+ ".wav",
9121
+ ".flac",
9122
+ ".aac",
9123
+ ".m4a",
9124
+ ".ogg",
9125
+ ".wma",
9126
+ ".opus"
9127
+ ]);
9128
+ VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
9129
+ ".mp4",
9130
+ ".mkv",
9131
+ ".avi",
9132
+ ".mov",
9133
+ ".webm",
9134
+ ".flv",
9135
+ ".wmv",
9136
+ ".m4v",
9137
+ ".ts"
9138
+ ]);
9139
+ ListenEngine = class extends EventEmitter {
9140
+ config;
9141
+ micProcess = null;
9142
+ liveTranscriber = null;
9143
+ // TranscribeLive from transcribe-cli
9144
+ active = false;
9145
+ silenceTimer = null;
9146
+ countdownInterval = null;
9147
+ lastTranscriptTime = 0;
9148
+ pendingText = "";
9149
+ blinkTimer = null;
9150
+ blinkState = false;
9151
+ transcribeCliAvailable = null;
9152
+ constructor(config) {
9153
+ super();
9154
+ this.config = {
9155
+ model: config?.model ?? "base",
9156
+ mode: config?.mode ?? "confirm",
9157
+ silenceTimeoutMs: config?.silenceTimeoutMs ?? 3e3
9158
+ };
9159
+ }
9160
+ get isActive() {
9161
+ return this.active;
9162
+ }
9163
+ get isBlinking() {
9164
+ return this.active && this.blinkState;
9165
+ }
9166
+ get currentModel() {
9167
+ return this.config.model;
9168
+ }
9169
+ get currentMode() {
9170
+ return this.config.mode;
9171
+ }
9172
+ get pendingTranscript() {
9173
+ return this.pendingText;
9174
+ }
9175
+ setModel(model) {
9176
+ this.config.model = model;
9177
+ }
9178
+ setMode(mode) {
9179
+ this.config.mode = mode;
9180
+ }
9181
+ /** Check if transcribe-cli is available. */
9182
+ async isAvailable() {
9183
+ if (this.transcribeCliAvailable !== null)
9184
+ return this.transcribeCliAvailable;
9185
+ try {
9186
+ const mod = await this.loadTranscribeCli();
9187
+ this.transcribeCliAvailable = mod !== null;
9188
+ } catch {
9189
+ this.transcribeCliAvailable = false;
9190
+ }
9191
+ if (!this.transcribeCliAvailable) {
9192
+ try {
9193
+ execSync10("which transcribe-cli", { stdio: "pipe" });
9194
+ this.transcribeCliAvailable = true;
9195
+ } catch {
9196
+ this.transcribeCliAvailable = false;
9197
+ }
9198
+ }
9199
+ return this.transcribeCliAvailable;
9200
+ }
9201
+ /** Load transcribe-cli dynamically (it may not be installed). */
9202
+ async loadTranscribeCli() {
9203
+ try {
9204
+ const globalRoot = execSync10("npm root -g", {
9205
+ encoding: "utf-8",
9206
+ timeout: 5e3,
9207
+ stdio: ["pipe", "pipe", "pipe"]
9208
+ }).trim();
9209
+ const tcPath = join16(globalRoot, "transcribe-cli");
9210
+ if (existsSync11(join16(tcPath, "dist", "index.js"))) {
9211
+ const { createRequire: createRequire4 } = await import("node:module");
9212
+ const req = createRequire4(import.meta.url);
9213
+ return req(join16(tcPath, "dist", "index.js"));
9214
+ }
9215
+ } catch {
9216
+ }
9217
+ const nvmBase = join16(homedir6(), ".nvm", "versions", "node");
9218
+ if (existsSync11(nvmBase)) {
9219
+ try {
9220
+ const { readdirSync: readdirSync9 } = await import("node:fs");
9221
+ for (const ver of readdirSync9(nvmBase)) {
9222
+ const tcPath = join16(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
9223
+ if (existsSync11(join16(tcPath, "dist", "index.js"))) {
9224
+ const { createRequire: createRequire4 } = await import("node:module");
9225
+ const req = createRequire4(import.meta.url);
9226
+ return req(join16(tcPath, "dist", "index.js"));
9227
+ }
9228
+ }
9229
+ } catch {
9230
+ }
9231
+ }
9232
+ return null;
9233
+ }
9234
+ /**
9235
+ * Start listening — captures microphone audio and feeds to TranscribeLive.
9236
+ */
9237
+ async start() {
9238
+ if (this.active)
9239
+ return "Already listening.";
9240
+ const micCmd = findMicCaptureCommand();
9241
+ if (!micCmd) {
9242
+ return "No microphone capture tool found. Install arecord (Linux), sox (macOS), or ffmpeg.";
9243
+ }
9244
+ let tc = await this.loadTranscribeCli();
9245
+ if (!tc) {
9246
+ this.emit("info", "transcribe-cli not found. Installing globally...");
9247
+ try {
9248
+ execSync10("npm i -g transcribe-cli", { stdio: "pipe", timeout: 12e4 });
9249
+ this.transcribeCliAvailable = null;
9250
+ tc = await this.loadTranscribeCli();
9251
+ } catch {
9252
+ }
9253
+ if (!tc) {
9254
+ return "Failed to install transcribe-cli. Try manually: npm i -g transcribe-cli";
9255
+ }
9256
+ }
9257
+ const TranscribeLive = tc.TranscribeLive;
9258
+ this.liveTranscriber = new TranscribeLive({
9259
+ model: this.config.model,
9260
+ sampleRate: 16e3,
9261
+ channels: 1,
9262
+ sampleWidth: 2,
9263
+ chunkDuration: 3
9264
+ // 3s chunks for responsive transcription
9265
+ });
9266
+ this.liveTranscriber.on("transcript", (evt) => {
9267
+ if (!evt.text.trim())
9268
+ return;
9269
+ this.lastTranscriptTime = Date.now();
9270
+ this.pendingText = evt.text.trim();
9271
+ this.emit("transcript", this.pendingText, evt.isFinal);
9272
+ if (this.config.mode === "auto") {
9273
+ this.resetSilenceTimer();
9274
+ }
9275
+ });
9276
+ this.liveTranscriber.on("error", (err) => {
9277
+ this.emit("error", err);
9278
+ });
9279
+ await new Promise((resolve16, reject) => {
9280
+ const timeout = setTimeout(() => reject(new Error("Model load timeout (60s)")), 6e4);
9281
+ this.liveTranscriber.on("ready", () => {
9282
+ clearTimeout(timeout);
9283
+ resolve16();
9284
+ });
9285
+ this.liveTranscriber.on("error", (err) => {
9286
+ clearTimeout(timeout);
9287
+ reject(err);
9288
+ });
9289
+ });
9290
+ this.micProcess = spawn5(micCmd.cmd, micCmd.args, {
9291
+ stdio: ["pipe", "pipe", "pipe"],
9292
+ env: { ...process.env }
9293
+ });
9294
+ this.micProcess.stdout?.on("data", (chunk) => {
9295
+ if (this.active && this.liveTranscriber) {
9296
+ this.liveTranscriber.write(chunk);
9297
+ }
9298
+ });
9299
+ this.micProcess.stderr?.on("data", () => {
9300
+ });
9301
+ this.micProcess.on("error", (err) => {
9302
+ this.emit("error", new Error(`Mic capture failed: ${err.message}`));
9303
+ this.stop();
9304
+ });
9305
+ this.micProcess.on("close", () => {
9306
+ if (this.active) {
9307
+ this.stop();
9308
+ }
9309
+ });
9310
+ this.active = true;
9311
+ this.blinkState = true;
9312
+ this.blinkTimer = setInterval(() => {
9313
+ this.blinkState = !this.blinkState;
9314
+ this.emit("recording", this.blinkState);
9315
+ }, 500);
9316
+ this.lastTranscriptTime = Date.now();
9317
+ this.emit("started");
9318
+ this.emit("recording", true);
9319
+ const modeDesc = this.config.mode === "auto" ? `auto (${this.config.silenceTimeoutMs / 1e3}s timeout)` : "confirm (press Enter to submit)";
9320
+ return `Listening with ${this.config.model} model (${modeDesc})`;
9321
+ }
9322
+ /**
9323
+ * Stop listening — cleanup mic, transcriber, timers.
9324
+ */
9325
+ async stop() {
9326
+ if (!this.active)
9327
+ return "Not listening.";
9328
+ this.active = false;
9329
+ this.blinkState = false;
9330
+ if (this.blinkTimer) {
9331
+ clearInterval(this.blinkTimer);
9332
+ this.blinkTimer = null;
9333
+ }
9334
+ if (this.silenceTimer) {
9335
+ clearTimeout(this.silenceTimer);
9336
+ this.silenceTimer = null;
9337
+ }
9338
+ if (this.countdownInterval) {
9339
+ clearInterval(this.countdownInterval);
9340
+ this.countdownInterval = null;
9341
+ }
9342
+ if (this.micProcess) {
9343
+ try {
9344
+ this.micProcess.kill("SIGTERM");
9345
+ } catch {
9346
+ }
9347
+ this.micProcess = null;
9348
+ }
9349
+ if (this.liveTranscriber) {
9350
+ try {
9351
+ this.liveTranscriber.stop();
9352
+ } catch {
9353
+ }
9354
+ this.liveTranscriber = null;
9355
+ }
9356
+ this.emit("recording", false);
9357
+ this.emit("stopped");
9358
+ const result = this.pendingText;
9359
+ this.pendingText = "";
9360
+ return result || "Stopped listening.";
9361
+ }
9362
+ /**
9363
+ * Accept the current pending transcript (for confirm mode).
9364
+ * Returns the text to inject into the input line.
9365
+ */
9366
+ acceptTranscript() {
9367
+ const text = this.pendingText;
9368
+ this.pendingText = "";
9369
+ return text;
9370
+ }
9371
+ /**
9372
+ * Transcribe a file (audio or video) using transcribe-cli.
9373
+ * Returns the transcription result.
9374
+ */
9375
+ async transcribeFile(filePath, outputDir) {
9376
+ let tc = await this.loadTranscribeCli();
9377
+ if (!tc) {
9378
+ try {
9379
+ execSync10("npm i -g transcribe-cli", { stdio: "pipe", timeout: 12e4 });
9380
+ this.transcribeCliAvailable = null;
9381
+ tc = await this.loadTranscribeCli();
9382
+ } catch {
9383
+ }
9384
+ }
9385
+ if (!tc)
9386
+ return null;
9387
+ try {
9388
+ const result = await tc.transcribe(filePath, {
9389
+ model: this.config.model,
9390
+ format: "json",
9391
+ diarize: false,
9392
+ wordTimestamps: false
9393
+ });
9394
+ if (outputDir) {
9395
+ const { basename: basename8 } = await import("node:path");
9396
+ const transcriptDir = join16(outputDir, ".oa", "transcripts");
9397
+ mkdirSync5(transcriptDir, { recursive: true });
9398
+ const outFile = join16(transcriptDir, `${basename8(filePath)}.txt`);
9399
+ writeFileSync5(outFile, result.text, "utf-8");
9400
+ }
9401
+ return {
9402
+ text: result.text,
9403
+ duration: result.duration,
9404
+ speakers: result.speakers,
9405
+ segments: result.segments
9406
+ };
9407
+ } catch (err) {
9408
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
9409
+ return null;
9410
+ }
9411
+ }
9412
+ // -------------------------------------------------------------------------
9413
+ // Auto-mode silence detection
9414
+ // -------------------------------------------------------------------------
9415
+ resetSilenceTimer() {
9416
+ if (this.silenceTimer)
9417
+ clearTimeout(this.silenceTimer);
9418
+ if (this.countdownInterval)
9419
+ clearInterval(this.countdownInterval);
9420
+ const timeoutMs = this.config.silenceTimeoutMs;
9421
+ const startTime = Date.now();
9422
+ this.countdownInterval = setInterval(() => {
9423
+ const elapsed = Date.now() - startTime;
9424
+ const remaining = Math.max(0, Math.ceil((timeoutMs - elapsed) / 1e3));
9425
+ this.emit("countdown", remaining);
9426
+ if (remaining <= 0 && this.countdownInterval) {
9427
+ clearInterval(this.countdownInterval);
9428
+ this.countdownInterval = null;
9429
+ }
9430
+ }, 1e3);
9431
+ this.silenceTimer = setTimeout(() => {
9432
+ if (this.active && this.pendingText) {
9433
+ this.emit("countdown", 0);
9434
+ this.emit("transcript", this.pendingText, true);
9435
+ }
9436
+ }, timeoutMs);
9437
+ }
9438
+ };
9439
+ _engine = null;
9440
+ }
9441
+ });
9442
+
8729
9443
  // packages/cli/dist/tui/model-picker.js
8730
9444
  async function fetchOllamaModels(baseUrl) {
8731
9445
  const url = `${baseUrl.replace(/\/$/, "")}/api/tags`;
@@ -9016,10 +9730,16 @@ function renderSlashHelp() {
9016
9730
  ["/dream deep", "Deep dream \u2014 multi-cycle exploration with sleep architecture"],
9017
9731
  ["/dream lucid", "Lucid dream \u2014 implements proposals, tests, evaluates in cycles"],
9018
9732
  ["/dream stop", "Wake up \u2014 stop dreaming"],
9733
+ ["/listen", "Toggle live microphone transcription (Whisper)"],
9734
+ ["/listen <model>", "Set model: tiny, base, small, medium, large"],
9735
+ ["/listen confirm", "Require Enter to submit transcription"],
9736
+ ["/listen auto", "Auto-submit after 3s silence (blinking \u25CF indicator)"],
9737
+ ["/listen stop", "Stop listening"],
9019
9738
  ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
9020
9739
  ["/tools", "List agent-created custom tools"],
9021
9740
  ["/skills", "List available AIWG skills"],
9022
9741
  ["/skills <keyword>", "Filter skills by name or trigger"],
9742
+ ["/<skill-name> [args]", "Invoke an AIWG skill directly"],
9023
9743
  ["/verbose", "Toggle verbose mode"],
9024
9744
  ["/clear", "Clear the screen"],
9025
9745
  ["/help", "Show this help"],
@@ -9149,6 +9869,10 @@ function formatToolArgs(toolName, args) {
9149
9869
  return String(args["region"] ?? "full screen");
9150
9870
  case "ocr":
9151
9871
  return `${args["path"] ?? ""}${args["region"] ? ` (${args["region"]})` : ""}`;
9872
+ case "transcribe_file":
9873
+ return `${args["path"] ?? ""}${args["model"] ? ` (${args["model"]})` : ""}`;
9874
+ case "transcribe_url":
9875
+ return truncStr(String(args["url"] ?? ""), 60);
9152
9876
  default:
9153
9877
  return Object.entries(args).map(([k, v]) => `${k}=${truncStr(String(v), 30)}`).join(", ");
9154
9878
  }
@@ -9214,7 +9938,10 @@ var init_render = __esm({
9214
9938
  // Image tools
9215
9939
  image_read: "\u{1F5BC}\uFE0F",
9216
9940
  screenshot: "\u{1F4F8}",
9217
- ocr: "\u{1F441}\uFE0F"
9941
+ ocr: "\u{1F441}\uFE0F",
9942
+ // Transcription tools
9943
+ transcribe_file: "\u{1F399}\uFE0F",
9944
+ transcribe_url: "\u{1F399}\uFE0F"
9218
9945
  };
9219
9946
  TOOL_LABELS = {
9220
9947
  file_read: "Read",
@@ -9245,7 +9972,10 @@ var init_render = __esm({
9245
9972
  // Image tools
9246
9973
  image_read: "Image read",
9247
9974
  screenshot: "Screenshot",
9248
- ocr: "OCR"
9975
+ ocr: "OCR",
9976
+ // Transcription tools
9977
+ transcribe_file: "Transcribe",
9978
+ transcribe_url: "Transcribe URL"
9249
9979
  };
9250
9980
  _contentWriteHook = null;
9251
9981
  HINTS = [
@@ -9287,6 +10017,8 @@ var init_render = __esm({
9287
10017
  "image_read",
9288
10018
  "screenshot",
9289
10019
  "ocr",
10020
+ "transcribe_file",
10021
+ "transcribe_url",
9290
10022
  "aiwg_setup",
9291
10023
  "aiwg_health",
9292
10024
  "aiwg_workflow",
@@ -9301,11 +10033,13 @@ var init_render = __esm({
9301
10033
  "/config",
9302
10034
  "/update",
9303
10035
  "/voice",
10036
+ "/listen",
9304
10037
  "/stream",
9305
10038
  "/verbose",
9306
10039
  "/dream",
9307
10040
  "/bruteforce",
9308
10041
  "/tools",
10042
+ "/skills",
9309
10043
  "/clear",
9310
10044
  "/help",
9311
10045
  "/quit"
@@ -9314,52 +10048,52 @@ var init_render = __esm({
9314
10048
  });
9315
10049
 
9316
10050
  // packages/cli/dist/tui/oa-directory.js
9317
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4, readdirSync as readdirSync6, statSync as statSync5, unlinkSync } from "node:fs";
9318
- import { join as join15, relative as relative2, basename as basename3, extname as extname4 } from "node:path";
9319
- import { homedir as homedir5 } from "node:os";
10051
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6, readdirSync as readdirSync6, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
10052
+ import { join as join17, relative as relative2, basename as basename4, extname as extname5 } from "node:path";
10053
+ import { homedir as homedir7 } from "node:os";
9320
10054
  function initOaDirectory(repoRoot) {
9321
- const oaPath = join15(repoRoot, OA_DIR);
10055
+ const oaPath = join17(repoRoot, OA_DIR);
9322
10056
  for (const sub of SUBDIRS) {
9323
- mkdirSync4(join15(oaPath, sub), { recursive: true });
10057
+ mkdirSync6(join17(oaPath, sub), { recursive: true });
9324
10058
  }
9325
10059
  return oaPath;
9326
10060
  }
9327
10061
  function hasOaDirectory(repoRoot) {
9328
- return existsSync10(join15(repoRoot, OA_DIR, "index"));
10062
+ return existsSync12(join17(repoRoot, OA_DIR, "index"));
9329
10063
  }
9330
10064
  function loadProjectSettings(repoRoot) {
9331
- const settingsPath = join15(repoRoot, OA_DIR, "settings.json");
10065
+ const settingsPath = join17(repoRoot, OA_DIR, "settings.json");
9332
10066
  try {
9333
- if (existsSync10(settingsPath)) {
9334
- return JSON.parse(readFileSync9(settingsPath, "utf-8"));
10067
+ if (existsSync12(settingsPath)) {
10068
+ return JSON.parse(readFileSync10(settingsPath, "utf-8"));
9335
10069
  }
9336
10070
  } catch {
9337
10071
  }
9338
10072
  return {};
9339
10073
  }
9340
10074
  function saveProjectSettings(repoRoot, settings) {
9341
- const oaPath = join15(repoRoot, OA_DIR);
9342
- mkdirSync4(oaPath, { recursive: true });
10075
+ const oaPath = join17(repoRoot, OA_DIR);
10076
+ mkdirSync6(oaPath, { recursive: true });
9343
10077
  const existing = loadProjectSettings(repoRoot);
9344
10078
  const merged = { ...existing, ...settings };
9345
- writeFileSync4(join15(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
10079
+ writeFileSync6(join17(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
9346
10080
  }
9347
10081
  function loadGlobalSettings() {
9348
- const settingsPath = join15(homedir5(), ".open-agents", "settings.json");
10082
+ const settingsPath = join17(homedir7(), ".open-agents", "settings.json");
9349
10083
  try {
9350
- if (existsSync10(settingsPath)) {
9351
- return JSON.parse(readFileSync9(settingsPath, "utf-8"));
10084
+ if (existsSync12(settingsPath)) {
10085
+ return JSON.parse(readFileSync10(settingsPath, "utf-8"));
9352
10086
  }
9353
10087
  } catch {
9354
10088
  }
9355
10089
  return {};
9356
10090
  }
9357
10091
  function saveGlobalSettings(settings) {
9358
- const dir = join15(homedir5(), ".open-agents");
9359
- mkdirSync4(dir, { recursive: true });
10092
+ const dir = join17(homedir7(), ".open-agents");
10093
+ mkdirSync6(dir, { recursive: true });
9360
10094
  const existing = loadGlobalSettings();
9361
10095
  const merged = { ...existing, ...settings };
9362
- writeFileSync4(join15(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
10096
+ writeFileSync6(join17(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
9363
10097
  }
9364
10098
  function resolveSettings(repoRoot) {
9365
10099
  const global = loadGlobalSettings();
@@ -9374,12 +10108,12 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9374
10108
  while (dir && !visited.has(dir)) {
9375
10109
  visited.add(dir);
9376
10110
  for (const name of CONTEXT_FILES) {
9377
- const filePath = join15(dir, name);
10111
+ const filePath = join17(dir, name);
9378
10112
  const normalizedName = name.toLowerCase();
9379
- if (existsSync10(filePath) && !seen.has(filePath)) {
10113
+ if (existsSync12(filePath) && !seen.has(filePath)) {
9380
10114
  seen.add(filePath);
9381
10115
  try {
9382
- let content = readFileSync9(filePath, "utf-8");
10116
+ let content = readFileSync10(filePath, "utf-8");
9383
10117
  if (content.length > maxContentLen) {
9384
10118
  content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
9385
10119
  }
@@ -9393,11 +10127,11 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9393
10127
  }
9394
10128
  }
9395
10129
  }
9396
- const projectMap = join15(dir, OA_DIR, "context", "project-map.md");
9397
- if (existsSync10(projectMap) && !seen.has(projectMap)) {
10130
+ const projectMap = join17(dir, OA_DIR, "context", "project-map.md");
10131
+ if (existsSync12(projectMap) && !seen.has(projectMap)) {
9398
10132
  seen.add(projectMap);
9399
10133
  try {
9400
- let content = readFileSync9(projectMap, "utf-8");
10134
+ let content = readFileSync10(projectMap, "utf-8");
9401
10135
  if (content.length > maxContentLen) {
9402
10136
  content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
9403
10137
  }
@@ -9409,7 +10143,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9409
10143
  } catch {
9410
10144
  }
9411
10145
  }
9412
- const parent = join15(dir, "..");
10146
+ const parent = join17(dir, "..");
9413
10147
  if (parent === dir)
9414
10148
  break;
9415
10149
  dir = parent;
@@ -9427,16 +10161,16 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
9427
10161
  return found;
9428
10162
  }
9429
10163
  function readIndexMeta(repoRoot) {
9430
- const metaPath = join15(repoRoot, OA_DIR, "index", "meta.json");
10164
+ const metaPath = join17(repoRoot, OA_DIR, "index", "meta.json");
9431
10165
  try {
9432
- return JSON.parse(readFileSync9(metaPath, "utf-8"));
10166
+ return JSON.parse(readFileSync10(metaPath, "utf-8"));
9433
10167
  } catch {
9434
10168
  return null;
9435
10169
  }
9436
10170
  }
9437
10171
  function generateProjectMap(repoRoot) {
9438
10172
  const sections = [];
9439
- const repoName2 = basename3(repoRoot);
10173
+ const repoName2 = basename4(repoRoot);
9440
10174
  sections.push(`# Project Map: ${repoName2}
9441
10175
  `);
9442
10176
  sections.push(`> Auto-generated by open-agents. Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
@@ -9480,28 +10214,28 @@ ${tree}\`\`\`
9480
10214
  sections.push("");
9481
10215
  }
9482
10216
  const content = sections.join("\n");
9483
- const contextDir = join15(repoRoot, OA_DIR, "context");
9484
- mkdirSync4(contextDir, { recursive: true });
9485
- writeFileSync4(join15(contextDir, "project-map.md"), content, "utf-8");
10217
+ const contextDir = join17(repoRoot, OA_DIR, "context");
10218
+ mkdirSync6(contextDir, { recursive: true });
10219
+ writeFileSync6(join17(contextDir, "project-map.md"), content, "utf-8");
9486
10220
  return content;
9487
10221
  }
9488
10222
  function saveSession(repoRoot, session) {
9489
- const historyDir = join15(repoRoot, OA_DIR, "history");
9490
- mkdirSync4(historyDir, { recursive: true });
9491
- writeFileSync4(join15(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
10223
+ const historyDir = join17(repoRoot, OA_DIR, "history");
10224
+ mkdirSync6(historyDir, { recursive: true });
10225
+ writeFileSync6(join17(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
9492
10226
  }
9493
10227
  function loadRecentSessions(repoRoot, limit = 5) {
9494
- const historyDir = join15(repoRoot, OA_DIR, "history");
9495
- if (!existsSync10(historyDir))
10228
+ const historyDir = join17(repoRoot, OA_DIR, "history");
10229
+ if (!existsSync12(historyDir))
9496
10230
  return [];
9497
10231
  try {
9498
10232
  const files = readdirSync6(historyDir).filter((f) => f.endsWith(".json")).map((f) => {
9499
- const stat3 = statSync5(join15(historyDir, f));
10233
+ const stat3 = statSync5(join17(historyDir, f));
9500
10234
  return { file: f, mtime: stat3.mtimeMs };
9501
10235
  }).sort((a, b) => b.mtime - a.mtime).slice(0, limit);
9502
10236
  return files.map((f) => {
9503
10237
  try {
9504
- return JSON.parse(readFileSync9(join15(historyDir, f.file), "utf-8"));
10238
+ return JSON.parse(readFileSync10(join17(historyDir, f.file), "utf-8"));
9505
10239
  } catch {
9506
10240
  return null;
9507
10241
  }
@@ -9511,18 +10245,18 @@ function loadRecentSessions(repoRoot, limit = 5) {
9511
10245
  }
9512
10246
  }
9513
10247
  function savePendingTask(repoRoot, task) {
9514
- const historyDir = join15(repoRoot, OA_DIR, "history");
9515
- mkdirSync4(historyDir, { recursive: true });
9516
- writeFileSync4(join15(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
10248
+ const historyDir = join17(repoRoot, OA_DIR, "history");
10249
+ mkdirSync6(historyDir, { recursive: true });
10250
+ writeFileSync6(join17(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
9517
10251
  }
9518
10252
  function loadPendingTask(repoRoot) {
9519
- const filePath = join15(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
10253
+ const filePath = join17(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
9520
10254
  try {
9521
- if (!existsSync10(filePath))
10255
+ if (!existsSync12(filePath))
9522
10256
  return null;
9523
- const data = JSON.parse(readFileSync9(filePath, "utf-8"));
10257
+ const data = JSON.parse(readFileSync10(filePath, "utf-8"));
9524
10258
  try {
9525
- unlinkSync(filePath);
10259
+ unlinkSync2(filePath);
9526
10260
  } catch {
9527
10261
  }
9528
10262
  return data;
@@ -9548,12 +10282,12 @@ function detectManifests(repoRoot) {
9548
10282
  { file: "docker-compose.yaml", type: "Docker Compose" }
9549
10283
  ];
9550
10284
  for (const check of checks) {
9551
- const filePath = join15(repoRoot, check.file);
9552
- if (existsSync10(filePath)) {
10285
+ const filePath = join17(repoRoot, check.file);
10286
+ if (existsSync12(filePath)) {
9553
10287
  let name;
9554
10288
  if (check.nameField) {
9555
10289
  try {
9556
- const data = JSON.parse(readFileSync9(filePath, "utf-8"));
10290
+ const data = JSON.parse(readFileSync10(filePath, "utf-8"));
9557
10291
  name = data[check.nameField];
9558
10292
  } catch {
9559
10293
  }
@@ -9582,7 +10316,7 @@ function findKeyFiles(repoRoot) {
9582
10316
  { pattern: "CLAUDE.md", description: "Claude Code context" }
9583
10317
  ];
9584
10318
  for (const check of checks) {
9585
- if (existsSync10(join15(repoRoot, check.pattern))) {
10319
+ if (existsSync12(join17(repoRoot, check.pattern))) {
9586
10320
  keyFiles.push({ path: check.pattern, description: check.description });
9587
10321
  }
9588
10322
  }
@@ -9608,12 +10342,12 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
9608
10342
  if (entry.isDirectory()) {
9609
10343
  let fileCount = 0;
9610
10344
  try {
9611
- fileCount = readdirSync6(join15(root, entry.name)).filter((f) => !f.startsWith(".")).length;
10345
+ fileCount = readdirSync6(join17(root, entry.name)).filter((f) => !f.startsWith(".")).length;
9612
10346
  } catch {
9613
10347
  }
9614
10348
  result += `${prefix}${connector}${entry.name}/ (${fileCount})
9615
10349
  `;
9616
- result += buildDirTree(join15(root, entry.name), maxDepth, childPrefix, depth + 1);
10350
+ result += buildDirTree(join17(root, entry.name), maxDepth, childPrefix, depth + 1);
9617
10351
  } else if (depth < maxDepth) {
9618
10352
  result += `${prefix}${connector}${entry.name}
9619
10353
  `;
@@ -9808,7 +10542,7 @@ async function handleSlashCommand(input, ctx) {
9808
10542
  }
9809
10543
  }
9810
10544
  process.stdout.write("\n");
9811
- renderInfo("The agent can use these via the skill_execute tool.");
10545
+ renderInfo('Invoke directly: /<skill-name> [args] (e.g. /ralph "fix tests" --completion "npm test passes")');
9812
10546
  renderInfo("Filter with: /skills <keyword>");
9813
10547
  }
9814
10548
  return "handled";
@@ -9829,6 +10563,38 @@ async function handleSlashCommand(input, ctx) {
9829
10563
  }
9830
10564
  return "handled";
9831
10565
  }
10566
+ case "listen":
10567
+ case "mic": {
10568
+ if (!ctx.listenToggle) {
10569
+ renderWarning("Listen mode not available in this context.");
10570
+ return "handled";
10571
+ }
10572
+ if (arg === "stop" || arg === "off") {
10573
+ const msg2 = await (ctx.listenStop?.() ?? Promise.resolve("Not listening."));
10574
+ renderInfo(msg2);
10575
+ return "handled";
10576
+ }
10577
+ if (arg === "confirm") {
10578
+ const msg2 = ctx.listenSetMode?.("confirm") ?? "Confirm mode set.";
10579
+ renderInfo(msg2);
10580
+ return "handled";
10581
+ }
10582
+ if (arg === "auto") {
10583
+ const msg2 = ctx.listenSetMode?.("auto") ?? "Auto mode set.";
10584
+ renderInfo(msg2);
10585
+ return "handled";
10586
+ }
10587
+ const modelSizes = ["tiny", "base", "small", "medium", "large", "large-v3"];
10588
+ if (arg && modelSizes.includes(arg.toLowerCase())) {
10589
+ const model = arg.toLowerCase() === "large" ? "large-v3" : arg.toLowerCase();
10590
+ const msg2 = await (ctx.listenSetModel?.(model) ?? Promise.resolve(`Model set to ${model}.`));
10591
+ renderInfo(msg2);
10592
+ return "handled";
10593
+ }
10594
+ const msg = await ctx.listenToggle();
10595
+ renderInfo(msg);
10596
+ return "handled";
10597
+ }
9832
10598
  case "bruteforce":
9833
10599
  case "brute": {
9834
10600
  const isOn = ctx.bruteForceToggle();
@@ -9837,9 +10603,19 @@ async function handleSlashCommand(input, ctx) {
9837
10603
  renderInfo(`Brute-force mode: ${isOn ? "on" : "off"}${hasLocal ? " (project-local)" : ""}` + (isOn ? " \u2014 agent will auto re-engage when turn limit is hit, reassess and try creative strategies" : ""));
9838
10604
  return "handled";
9839
10605
  }
9840
- default:
10606
+ default: {
10607
+ const skills = discoverSkills(ctx.repoRoot);
10608
+ const skill = skills.find((s) => s.name === cmd || s.name === cmd.replace(/_/g, "-"));
10609
+ if (skill) {
10610
+ const content = loadSkillContent(skill.filePath);
10611
+ if (content) {
10612
+ renderInfo(`Loading skill: ${c2.bold(skill.name)} (${skill.source})`);
10613
+ return { type: "skill", name: skill.name, content, args: arg };
10614
+ }
10615
+ }
9841
10616
  renderWarning(`Unknown command: /${cmd}. Type /help for available commands.`);
9842
10617
  return "handled";
10618
+ }
9843
10619
  }
9844
10620
  }
9845
10621
  async function listModels(ctx) {
@@ -9971,17 +10747,17 @@ async function handleUpdate(subcommand, repoRoot, savePendingTaskState) {
9971
10747
  try {
9972
10748
  const { createRequire: createRequire4 } = await import("node:module");
9973
10749
  const { fileURLToPath: fileURLToPath3 } = await import("node:url");
9974
- const { dirname: dirname5, join: join26 } = await import("node:path");
9975
- const { existsSync: existsSync17 } = await import("node:fs");
10750
+ const { dirname: dirname5, join: join28 } = await import("node:path");
10751
+ const { existsSync: existsSync19 } = await import("node:fs");
9976
10752
  const req = createRequire4(import.meta.url);
9977
10753
  const thisDir = dirname5(fileURLToPath3(import.meta.url));
9978
10754
  const candidates = [
9979
- join26(thisDir, "..", "package.json"),
9980
- join26(thisDir, "..", "..", "package.json"),
9981
- join26(thisDir, "..", "..", "..", "package.json")
10755
+ join28(thisDir, "..", "package.json"),
10756
+ join28(thisDir, "..", "..", "package.json"),
10757
+ join28(thisDir, "..", "..", "..", "package.json")
9982
10758
  ];
9983
10759
  for (const pkgPath of candidates) {
9984
- if (existsSync17(pkgPath)) {
10760
+ if (existsSync19(pkgPath)) {
9985
10761
  const pkg = req(pkgPath);
9986
10762
  if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
9987
10763
  currentVersion = pkg.version ?? "0.0.0";
@@ -10065,17 +10841,17 @@ var init_commands = __esm({
10065
10841
 
10066
10842
  // packages/cli/dist/tui/setup.js
10067
10843
  import * as readline from "node:readline";
10068
- import { execSync as execSync9 } from "node:child_process";
10069
- import { existsSync as existsSync11, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "node:fs";
10070
- import { join as join16 } from "node:path";
10071
- import { homedir as homedir6 } from "node:os";
10844
+ import { execSync as execSync11 } from "node:child_process";
10845
+ import { existsSync as existsSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "node:fs";
10846
+ import { join as join18 } from "node:path";
10847
+ import { homedir as homedir8 } from "node:os";
10072
10848
  function detectSystemSpecs() {
10073
10849
  let totalRamGB = 0;
10074
10850
  let availableRamGB = 0;
10075
10851
  let gpuVramGB = 0;
10076
10852
  let gpuName = "";
10077
10853
  try {
10078
- const memInfo = execSync9("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
10854
+ const memInfo = execSync11("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
10079
10855
  encoding: "utf8",
10080
10856
  timeout: 5e3
10081
10857
  });
@@ -10095,7 +10871,7 @@ function detectSystemSpecs() {
10095
10871
  } catch {
10096
10872
  }
10097
10873
  try {
10098
- const nvidiaSmi = execSync9("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
10874
+ const nvidiaSmi = execSync11("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
10099
10875
  const lines = nvidiaSmi.trim().split("\n");
10100
10876
  if (lines.length > 0) {
10101
10877
  for (const line of lines) {
@@ -10151,13 +10927,13 @@ function modelSupportsToolCalling(modelName) {
10151
10927
  return false;
10152
10928
  }
10153
10929
  function ask(rl, question) {
10154
- return new Promise((resolve15) => {
10155
- rl.question(question, (answer) => resolve15(answer.trim()));
10930
+ return new Promise((resolve16) => {
10931
+ rl.question(question, (answer) => resolve16(answer.trim()));
10156
10932
  });
10157
10933
  }
10158
10934
  function pullModelWithAutoUpdate(tag) {
10159
10935
  try {
10160
- execSync9(`ollama pull ${tag}`, {
10936
+ execSync11(`ollama pull ${tag}`, {
10161
10937
  stdio: "inherit",
10162
10938
  timeout: 36e5
10163
10939
  // 1 hour max
@@ -10174,7 +10950,7 @@ function pullModelWithAutoUpdate(tag) {
10174
10950
 
10175
10951
  `);
10176
10952
  try {
10177
- execSync9("curl -fsSL https://ollama.com/install.sh | sh", {
10953
+ execSync11("curl -fsSL https://ollama.com/install.sh | sh", {
10178
10954
  stdio: "inherit",
10179
10955
  timeout: 3e5
10180
10956
  // 5 min max for install
@@ -10185,7 +10961,7 @@ function pullModelWithAutoUpdate(tag) {
10185
10961
  process.stdout.write(` ${c2.cyan("\u25CF")} Retrying pull of ${c2.bold(tag)}...
10186
10962
 
10187
10963
  `);
10188
- execSync9(`ollama pull ${tag}`, {
10964
+ execSync11(`ollama pull ${tag}`, {
10189
10965
  stdio: "inherit",
10190
10966
  timeout: 36e5
10191
10967
  });
@@ -10363,12 +11139,12 @@ async function doSetup(config, rl) {
10363
11139
  `PARAMETER num_predict 16384`,
10364
11140
  `PARAMETER stop "<|endoftext|>"`
10365
11141
  ].join("\n");
10366
- const modelDir2 = join16(homedir6(), ".open-agents", "models");
10367
- mkdirSync5(modelDir2, { recursive: true });
10368
- const modelfilePath = join16(modelDir2, `Modelfile.${customName}`);
10369
- writeFileSync5(modelfilePath, modelfileContent + "\n", "utf8");
11142
+ const modelDir2 = join18(homedir8(), ".open-agents", "models");
11143
+ mkdirSync7(modelDir2, { recursive: true });
11144
+ const modelfilePath = join18(modelDir2, `Modelfile.${customName}`);
11145
+ writeFileSync7(modelfilePath, modelfileContent + "\n", "utf8");
10370
11146
  process.stdout.write(` ${c2.dim("Creating model...")} `);
10371
- execSync9(`ollama create ${customName} -f ${modelfilePath}`, {
11147
+ execSync11(`ollama create ${customName} -f ${modelfilePath}`, {
10372
11148
  stdio: "pipe",
10373
11149
  timeout: 12e4
10374
11150
  });
@@ -10411,7 +11187,7 @@ async function isModelAvailable(config) {
10411
11187
  }
10412
11188
  function isFirstRun() {
10413
11189
  try {
10414
- return !existsSync11(join16(homedir6(), ".open-agents", "config.json"));
11190
+ return !existsSync13(join18(homedir8(), ".open-agents", "config.json"));
10415
11191
  } catch {
10416
11192
  return true;
10417
11193
  }
@@ -10451,10 +11227,10 @@ var init_setup = __esm({
10451
11227
  });
10452
11228
 
10453
11229
  // packages/cli/dist/tui/project-context.js
10454
- import { existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync7 } from "node:fs";
10455
- import { join as join17, basename as basename4 } from "node:path";
10456
- import { execSync as execSync10 } from "node:child_process";
10457
- import { homedir as homedir7, platform, release } from "node:os";
11230
+ import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "node:fs";
11231
+ import { join as join19, basename as basename5 } from "node:path";
11232
+ import { execSync as execSync12 } from "node:child_process";
11233
+ import { homedir as homedir9, platform, release } from "node:os";
10458
11234
  function loadProjectFiles(repoRoot) {
10459
11235
  const discovered = discoverContextFiles(repoRoot);
10460
11236
  if (discovered.length === 0)
@@ -10472,10 +11248,10 @@ function loadProjectMap(repoRoot) {
10472
11248
  if (!hasOaDirectory(repoRoot)) {
10473
11249
  initOaDirectory(repoRoot);
10474
11250
  }
10475
- const mapPath = join17(repoRoot, OA_DIR, "context", "project-map.md");
10476
- if (existsSync12(mapPath)) {
11251
+ const mapPath = join19(repoRoot, OA_DIR, "context", "project-map.md");
11252
+ if (existsSync14(mapPath)) {
10477
11253
  try {
10478
- const content = readFileSync10(mapPath, "utf-8");
11254
+ const content = readFileSync11(mapPath, "utf-8");
10479
11255
  return content;
10480
11256
  } catch {
10481
11257
  }
@@ -10484,19 +11260,19 @@ function loadProjectMap(repoRoot) {
10484
11260
  }
10485
11261
  function getGitInfo(repoRoot) {
10486
11262
  try {
10487
- execSync10("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
11263
+ execSync12("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
10488
11264
  } catch {
10489
11265
  return "";
10490
11266
  }
10491
11267
  const lines = [];
10492
11268
  try {
10493
- const branch = execSync10("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
11269
+ const branch = execSync12("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
10494
11270
  if (branch)
10495
11271
  lines.push(`Branch: ${branch}`);
10496
11272
  } catch {
10497
11273
  }
10498
11274
  try {
10499
- const status = execSync10("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
11275
+ const status = execSync12("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
10500
11276
  if (status) {
10501
11277
  const changed = status.split("\n").length;
10502
11278
  lines.push(`Working tree: ${changed} changed file(s)`);
@@ -10506,7 +11282,7 @@ function getGitInfo(repoRoot) {
10506
11282
  } catch {
10507
11283
  }
10508
11284
  try {
10509
- const log = execSync10("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
11285
+ const log = execSync12("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
10510
11286
  if (log)
10511
11287
  lines.push(`Recent commits:
10512
11288
  ${log}`);
@@ -10516,33 +11292,33 @@ ${log}`);
10516
11292
  }
10517
11293
  function loadMemoryContext(repoRoot) {
10518
11294
  const sections = [];
10519
- const oaMemDir = join17(repoRoot, OA_DIR, "memory");
11295
+ const oaMemDir = join19(repoRoot, OA_DIR, "memory");
10520
11296
  const oaEntries = loadMemoryDir(oaMemDir, "project");
10521
11297
  if (oaEntries)
10522
11298
  sections.push(oaEntries);
10523
- const legacyMemDir = join17(repoRoot, ".open-agents", "memory");
10524
- if (legacyMemDir !== oaMemDir && existsSync12(legacyMemDir)) {
11299
+ const legacyMemDir = join19(repoRoot, ".open-agents", "memory");
11300
+ if (legacyMemDir !== oaMemDir && existsSync14(legacyMemDir)) {
10525
11301
  const legacyEntries = loadMemoryDir(legacyMemDir, "project/legacy");
10526
11302
  if (legacyEntries)
10527
11303
  sections.push(legacyEntries);
10528
11304
  }
10529
- const globalMemDir = join17(homedir7(), ".open-agents", "memory");
11305
+ const globalMemDir = join19(homedir9(), ".open-agents", "memory");
10530
11306
  const globalEntries = loadMemoryDir(globalMemDir, "global");
10531
11307
  if (globalEntries)
10532
11308
  sections.push(globalEntries);
10533
11309
  return sections.join("\n\n");
10534
11310
  }
10535
11311
  function loadMemoryDir(memDir, scope) {
10536
- if (!existsSync12(memDir))
11312
+ if (!existsSync14(memDir))
10537
11313
  return "";
10538
11314
  const lines = [];
10539
11315
  try {
10540
11316
  const files = readdirSync7(memDir).filter((f) => f.endsWith(".json"));
10541
11317
  for (const file of files.slice(0, 10)) {
10542
11318
  try {
10543
- const raw = readFileSync10(join17(memDir, file), "utf-8");
11319
+ const raw = readFileSync11(join19(memDir, file), "utf-8");
10544
11320
  const entries = JSON.parse(raw);
10545
- const topic = basename4(file, ".json");
11321
+ const topic = basename5(file, ".json");
10546
11322
  const keys = Object.keys(entries);
10547
11323
  if (keys.length === 0)
10548
11324
  continue;
@@ -11551,25 +12327,25 @@ var init_carousel = __esm({
11551
12327
  });
11552
12328
 
11553
12329
  // packages/cli/dist/tui/voice.js
11554
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync2 } from "node:fs";
11555
- import { join as join18 } from "node:path";
11556
- import { homedir as homedir8, tmpdir as tmpdir2, platform as platform2 } from "node:os";
11557
- import { execSync as execSync11, spawn as nodeSpawn } from "node:child_process";
12330
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8, readFileSync as readFileSync12, unlinkSync as unlinkSync3 } from "node:fs";
12331
+ import { join as join20 } from "node:path";
12332
+ import { homedir as homedir10, tmpdir as tmpdir2, platform as platform2 } from "node:os";
12333
+ import { execSync as execSync13, spawn as nodeSpawn } from "node:child_process";
11558
12334
  import { createRequire } from "node:module";
11559
12335
  function voiceDir() {
11560
- return join18(homedir8(), ".open-agents", "voice");
12336
+ return join20(homedir10(), ".open-agents", "voice");
11561
12337
  }
11562
12338
  function modelsDir() {
11563
- return join18(voiceDir(), "models");
12339
+ return join20(voiceDir(), "models");
11564
12340
  }
11565
12341
  function modelDir(id) {
11566
- return join18(modelsDir(), id);
12342
+ return join20(modelsDir(), id);
11567
12343
  }
11568
12344
  function modelOnnxPath(id) {
11569
- return join18(modelDir(id), "model.onnx");
12345
+ return join20(modelDir(id), "model.onnx");
11570
12346
  }
11571
12347
  function modelConfigPath(id) {
11572
- return join18(modelDir(id), "config.json");
12348
+ return join20(modelDir(id), "config.json");
11573
12349
  }
11574
12350
  function describeToolCall(toolName, args) {
11575
12351
  const path = args["path"];
@@ -11842,11 +12618,11 @@ var init_voice = __esm({
11842
12618
  const audioData = result["output"].data;
11843
12619
  if (audioData.length === 0)
11844
12620
  return;
11845
- const wavPath = join18(tmpdir2(), `oa-voice-${Date.now()}.wav`);
12621
+ const wavPath = join20(tmpdir2(), `oa-voice-${Date.now()}.wav`);
11846
12622
  this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
11847
12623
  await this.playWav(wavPath);
11848
12624
  try {
11849
- unlinkSync2(wavPath);
12625
+ unlinkSync3(wavPath);
11850
12626
  } catch {
11851
12627
  }
11852
12628
  }
@@ -11922,7 +12698,7 @@ var init_voice = __esm({
11922
12698
  buffer.write("data", 36);
11923
12699
  buffer.writeUInt32LE(dataSize, 40);
11924
12700
  Buffer.from(int16.buffer, int16.byteOffset, int16.byteLength).copy(buffer, 44);
11925
- writeFileSync6(path, buffer);
12701
+ writeFileSync8(path, buffer);
11926
12702
  }
11927
12703
  // -------------------------------------------------------------------------
11928
12704
  // Audio playback (system default speakers)
@@ -11931,7 +12707,7 @@ var init_voice = __esm({
11931
12707
  const cmd = this.getPlayCommand(path);
11932
12708
  if (!cmd)
11933
12709
  return;
11934
- return new Promise((resolve15) => {
12710
+ return new Promise((resolve16) => {
11935
12711
  const child = nodeSpawn(cmd[0], cmd.slice(1), {
11936
12712
  stdio: "ignore",
11937
12713
  detached: false
@@ -11940,12 +12716,12 @@ var init_voice = __esm({
11940
12716
  child.on("close", () => {
11941
12717
  if (this.currentPlayback === child)
11942
12718
  this.currentPlayback = null;
11943
- resolve15();
12719
+ resolve16();
11944
12720
  });
11945
12721
  child.on("error", () => {
11946
12722
  if (this.currentPlayback === child)
11947
12723
  this.currentPlayback = null;
11948
- resolve15();
12724
+ resolve16();
11949
12725
  });
11950
12726
  setTimeout(() => {
11951
12727
  if (this.currentPlayback === child) {
@@ -11955,7 +12731,7 @@ var init_voice = __esm({
11955
12731
  }
11956
12732
  this.currentPlayback = null;
11957
12733
  }
11958
- resolve15();
12734
+ resolve16();
11959
12735
  }, 15e3);
11960
12736
  });
11961
12737
  }
@@ -11972,7 +12748,7 @@ var init_voice = __esm({
11972
12748
  }
11973
12749
  for (const player of ["paplay", "pw-play", "aplay"]) {
11974
12750
  try {
11975
- execSync11(`which ${player}`, { stdio: "pipe" });
12751
+ execSync13(`which ${player}`, { stdio: "pipe" });
11976
12752
  return [player, path];
11977
12753
  } catch {
11978
12754
  }
@@ -11994,36 +12770,36 @@ var init_voice = __esm({
11994
12770
  async ensureRuntime() {
11995
12771
  if (this.ort)
11996
12772
  return;
11997
- mkdirSync6(voiceDir(), { recursive: true });
11998
- const pkgPath = join18(voiceDir(), "package.json");
12773
+ mkdirSync8(voiceDir(), { recursive: true });
12774
+ const pkgPath = join20(voiceDir(), "package.json");
11999
12775
  const expectedDeps = {
12000
12776
  "onnxruntime-node": "^1.21.0",
12001
12777
  "phonemizer": "^1.2.1"
12002
12778
  };
12003
- if (existsSync13(pkgPath)) {
12779
+ if (existsSync15(pkgPath)) {
12004
12780
  try {
12005
- const existing = JSON.parse(readFileSync11(pkgPath, "utf8"));
12781
+ const existing = JSON.parse(readFileSync12(pkgPath, "utf8"));
12006
12782
  if (!existing.dependencies?.["phonemizer"]) {
12007
12783
  existing.dependencies = { ...existing.dependencies, ...expectedDeps };
12008
- writeFileSync6(pkgPath, JSON.stringify(existing, null, 2));
12784
+ writeFileSync8(pkgPath, JSON.stringify(existing, null, 2));
12009
12785
  }
12010
12786
  } catch {
12011
12787
  }
12012
12788
  }
12013
- if (!existsSync13(pkgPath)) {
12014
- writeFileSync6(pkgPath, JSON.stringify({
12789
+ if (!existsSync15(pkgPath)) {
12790
+ writeFileSync8(pkgPath, JSON.stringify({
12015
12791
  name: "open-agents-voice",
12016
12792
  private: true,
12017
12793
  dependencies: expectedDeps
12018
12794
  }, null, 2));
12019
12795
  }
12020
- const voiceRequire = createRequire(join18(voiceDir(), "index.js"));
12796
+ const voiceRequire = createRequire(join20(voiceDir(), "index.js"));
12021
12797
  try {
12022
12798
  this.ort = voiceRequire("onnxruntime-node");
12023
12799
  } catch {
12024
12800
  renderInfo("Installing ONNX runtime for voice synthesis...");
12025
12801
  try {
12026
- execSync11("npm install --no-audit --no-fund", {
12802
+ execSync13("npm install --no-audit --no-fund", {
12027
12803
  cwd: voiceDir(),
12028
12804
  stdio: "pipe",
12029
12805
  timeout: 12e4
@@ -12040,7 +12816,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12040
12816
  } catch {
12041
12817
  renderInfo("Installing phonemizer for voice synthesis...");
12042
12818
  try {
12043
- execSync11("npm install --no-audit --no-fund", {
12819
+ execSync13("npm install --no-audit --no-fund", {
12044
12820
  cwd: voiceDir(),
12045
12821
  stdio: "pipe",
12046
12822
  timeout: 12e4
@@ -12063,18 +12839,18 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12063
12839
  const dir = modelDir(id);
12064
12840
  const onnxPath = modelOnnxPath(id);
12065
12841
  const configPath = modelConfigPath(id);
12066
- if (existsSync13(onnxPath) && existsSync13(configPath))
12842
+ if (existsSync15(onnxPath) && existsSync15(configPath))
12067
12843
  return;
12068
- mkdirSync6(dir, { recursive: true });
12069
- if (!existsSync13(configPath)) {
12844
+ mkdirSync8(dir, { recursive: true });
12845
+ if (!existsSync15(configPath)) {
12070
12846
  renderInfo(`Downloading ${model.label} voice config...`);
12071
12847
  const configResp = await fetch(model.configUrl);
12072
12848
  if (!configResp.ok)
12073
12849
  throw new Error(`Failed to download config: HTTP ${configResp.status}`);
12074
12850
  const configText = await configResp.text();
12075
- writeFileSync6(configPath, configText);
12851
+ writeFileSync8(configPath, configText);
12076
12852
  }
12077
- if (!existsSync13(onnxPath)) {
12853
+ if (!existsSync15(onnxPath)) {
12078
12854
  renderInfo(`Downloading ${model.label} voice model (this may take a minute)...`);
12079
12855
  const onnxResp = await fetch(model.onnxUrl);
12080
12856
  if (!onnxResp.ok)
@@ -12098,7 +12874,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12098
12874
  }
12099
12875
  process.stdout.write("\r" + " ".repeat(60) + "\r");
12100
12876
  const fullBuffer = Buffer.concat(chunks);
12101
- writeFileSync6(onnxPath, fullBuffer);
12877
+ writeFileSync8(onnxPath, fullBuffer);
12102
12878
  renderInfo(`${model.label} model downloaded (${formatBytes2(fullBuffer.length)}).`);
12103
12879
  }
12104
12880
  }
@@ -12110,10 +12886,10 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
12110
12886
  throw new Error("ONNX runtime not loaded");
12111
12887
  const onnxPath = modelOnnxPath(this.modelId);
12112
12888
  const configPath = modelConfigPath(this.modelId);
12113
- if (!existsSync13(onnxPath) || !existsSync13(configPath)) {
12889
+ if (!existsSync15(onnxPath) || !existsSync15(configPath)) {
12114
12890
  throw new Error(`Model files not found for ${this.modelId}`);
12115
12891
  }
12116
- this.config = JSON.parse(readFileSync11(configPath, "utf8"));
12892
+ this.config = JSON.parse(readFileSync12(configPath, "utf8"));
12117
12893
  renderInfo("Loading voice model...");
12118
12894
  this.session = await this.ort.InferenceSession.create(onnxPath, {
12119
12895
  executionProviders: ["cpu"],
@@ -12570,13 +13346,13 @@ var init_stream_renderer = __esm({
12570
13346
  });
12571
13347
 
12572
13348
  // packages/cli/dist/tui/edit-history.js
12573
- import { appendFileSync, mkdirSync as mkdirSync7 } from "node:fs";
12574
- import { join as join19 } from "node:path";
13349
+ import { appendFileSync, mkdirSync as mkdirSync9 } from "node:fs";
13350
+ import { join as join21 } from "node:path";
12575
13351
  function createEditHistoryLogger(repoRoot, sessionId) {
12576
- const historyDir = join19(repoRoot, ".oa", "history");
12577
- const logPath = join19(historyDir, "edits.jsonl");
13352
+ const historyDir = join21(repoRoot, ".oa", "history");
13353
+ const logPath = join21(historyDir, "edits.jsonl");
12578
13354
  try {
12579
- mkdirSync7(historyDir, { recursive: true });
13355
+ mkdirSync9(historyDir, { recursive: true });
12580
13356
  } catch {
12581
13357
  }
12582
13358
  function logToolCall(toolName, toolArgs, success) {
@@ -12685,9 +13461,9 @@ var init_edit_history = __esm({
12685
13461
  });
12686
13462
 
12687
13463
  // packages/cli/dist/tui/dream-engine.js
12688
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, readFileSync as readFileSync12, existsSync as existsSync14, cpSync, rmSync, readdirSync as readdirSync8 } from "node:fs";
12689
- import { join as join20, basename as basename5 } from "node:path";
12690
- import { execSync as execSync12 } from "node:child_process";
13464
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync13, existsSync as existsSync16, cpSync, rmSync, readdirSync as readdirSync8 } from "node:fs";
13465
+ import { join as join22, basename as basename6 } from "node:path";
13466
+ import { execSync as execSync14 } from "node:child_process";
12691
13467
  function adaptTool(tool) {
12692
13468
  return {
12693
13469
  name: tool.name,
@@ -12861,14 +13637,14 @@ var init_dream_engine = __esm({
12861
13637
  const content = String(args["content"] ?? "");
12862
13638
  if (!rawPath)
12863
13639
  return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
12864
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join20(this.dreamsDir, basename5(rawPath)) : join20(this.dreamsDir, rawPath);
13640
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join22(this.dreamsDir, basename6(rawPath)) : join22(this.dreamsDir, rawPath);
12865
13641
  if (!targetPath.startsWith(this.dreamsDir)) {
12866
13642
  return { success: false, output: "", error: "Dream mode: writes are confined to .oa/dreams/", durationMs: Date.now() - start };
12867
13643
  }
12868
13644
  try {
12869
- const dir = join20(targetPath, "..");
12870
- mkdirSync8(dir, { recursive: true });
12871
- writeFileSync7(targetPath, content, "utf-8");
13645
+ const dir = join22(targetPath, "..");
13646
+ mkdirSync10(dir, { recursive: true });
13647
+ writeFileSync9(targetPath, content, "utf-8");
12872
13648
  return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
12873
13649
  } catch (err) {
12874
13650
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -12896,20 +13672,20 @@ var init_dream_engine = __esm({
12896
13672
  const rawPath = String(args["path"] ?? "");
12897
13673
  const oldStr = String(args["old_string"] ?? "");
12898
13674
  const newStr = String(args["new_string"] ?? "");
12899
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join20(this.dreamsDir, basename5(rawPath)) : join20(this.dreamsDir, rawPath);
13675
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join22(this.dreamsDir, basename6(rawPath)) : join22(this.dreamsDir, rawPath);
12900
13676
  if (!targetPath.startsWith(this.dreamsDir)) {
12901
13677
  return { success: false, output: "", error: "Dream mode: edits are confined to .oa/dreams/", durationMs: Date.now() - start };
12902
13678
  }
12903
13679
  try {
12904
- if (!existsSync14(targetPath)) {
13680
+ if (!existsSync16(targetPath)) {
12905
13681
  return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
12906
13682
  }
12907
- let content = readFileSync12(targetPath, "utf-8");
13683
+ let content = readFileSync13(targetPath, "utf-8");
12908
13684
  if (!content.includes(oldStr)) {
12909
13685
  return { success: false, output: "", error: "old_string not found in file", durationMs: Date.now() - start };
12910
13686
  }
12911
13687
  content = content.replace(oldStr, newStr);
12912
- writeFileSync7(targetPath, content, "utf-8");
13688
+ writeFileSync9(targetPath, content, "utf-8");
12913
13689
  return { success: true, output: `Edited ${rawPath}`, durationMs: Date.now() - start };
12914
13690
  } catch (err) {
12915
13691
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -12940,7 +13716,7 @@ var init_dream_engine = __esm({
12940
13716
  }
12941
13717
  }
12942
13718
  try {
12943
- const output = execSync12(cmd, {
13719
+ const output = execSync14(cmd, {
12944
13720
  cwd: this.repoRoot,
12945
13721
  timeout: 3e4,
12946
13722
  encoding: "utf-8",
@@ -12963,7 +13739,7 @@ var init_dream_engine = __esm({
12963
13739
  constructor(config, repoRoot) {
12964
13740
  this.config = config;
12965
13741
  this.repoRoot = repoRoot;
12966
- this.dreamsDir = join20(repoRoot, ".oa", "dreams");
13742
+ this.dreamsDir = join22(repoRoot, ".oa", "dreams");
12967
13743
  this.state = {
12968
13744
  mode: "default",
12969
13745
  active: false,
@@ -12994,7 +13770,7 @@ var init_dream_engine = __esm({
12994
13770
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
12995
13771
  results: []
12996
13772
  };
12997
- mkdirSync8(this.dreamsDir, { recursive: true });
13773
+ mkdirSync10(this.dreamsDir, { recursive: true });
12998
13774
  this.saveDreamState();
12999
13775
  try {
13000
13776
  for (let cycle = 1; cycle <= totalCycles; cycle++) {
@@ -13035,8 +13811,8 @@ ${result.summary}`;
13035
13811
  if (mode !== "default" || cycle === totalCycles) {
13036
13812
  renderDreamContraction(cycle);
13037
13813
  const cycleSummary = this.buildCycleSummary(cycle, previousFindings);
13038
- const summaryPath = join20(this.dreamsDir, `cycle-${cycle}-summary.md`);
13039
- writeFileSync7(summaryPath, cycleSummary, "utf-8");
13814
+ const summaryPath = join22(this.dreamsDir, `cycle-${cycle}-summary.md`);
13815
+ writeFileSync9(summaryPath, cycleSummary, "utf-8");
13040
13816
  }
13041
13817
  if (mode === "lucid" && !this.abortController.signal.aborted) {
13042
13818
  this.saveVersionCheckpoint(cycle);
@@ -13156,29 +13932,29 @@ Dreams directory: ${this.dreamsDir}`);
13156
13932
  }
13157
13933
  /** Save workspace backup for lucid mode */
13158
13934
  saveVersionCheckpoint(cycle) {
13159
- const checkpointDir = join20(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
13935
+ const checkpointDir = join22(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
13160
13936
  try {
13161
- mkdirSync8(checkpointDir, { recursive: true });
13937
+ mkdirSync10(checkpointDir, { recursive: true });
13162
13938
  try {
13163
- const gitStatus = execSync12("git status --porcelain", {
13939
+ const gitStatus = execSync14("git status --porcelain", {
13164
13940
  cwd: this.repoRoot,
13165
13941
  encoding: "utf-8",
13166
13942
  timeout: 1e4
13167
13943
  });
13168
- const gitDiff = execSync12("git diff", {
13944
+ const gitDiff = execSync14("git diff", {
13169
13945
  cwd: this.repoRoot,
13170
13946
  encoding: "utf-8",
13171
13947
  timeout: 1e4
13172
13948
  });
13173
- const gitHash = execSync12("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
13949
+ const gitHash = execSync14("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
13174
13950
  cwd: this.repoRoot,
13175
13951
  encoding: "utf-8",
13176
13952
  timeout: 5e3
13177
13953
  }).trim();
13178
- writeFileSync7(join20(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
13179
- writeFileSync7(join20(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
13180
- writeFileSync7(join20(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
13181
- writeFileSync7(join20(checkpointDir, "checkpoint.json"), JSON.stringify({
13954
+ writeFileSync9(join22(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
13955
+ writeFileSync9(join22(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
13956
+ writeFileSync9(join22(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
13957
+ writeFileSync9(join22(checkpointDir, "checkpoint.json"), JSON.stringify({
13182
13958
  cycle,
13183
13959
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13184
13960
  gitHash,
@@ -13186,7 +13962,7 @@ Dreams directory: ${this.dreamsDir}`);
13186
13962
  }, null, 2), "utf-8");
13187
13963
  renderInfo(`Checkpoint saved: cycle ${cycle} (${gitHash.slice(0, 8)})`);
13188
13964
  } catch {
13189
- writeFileSync7(join20(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
13965
+ writeFileSync9(join22(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
13190
13966
  renderInfo(`Checkpoint saved: cycle ${cycle} (no git)`);
13191
13967
  }
13192
13968
  } catch (err) {
@@ -13244,14 +14020,14 @@ ${files.map((f) => `- [\`${f}\`](./${f})`).join("\n")}
13244
14020
  ---
13245
14021
  *Auto-generated by open-agents dream engine*
13246
14022
  `;
13247
- writeFileSync7(join20(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
14023
+ writeFileSync9(join22(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
13248
14024
  } catch {
13249
14025
  }
13250
14026
  }
13251
14027
  /** Save dream state for resume/inspection */
13252
14028
  saveDreamState() {
13253
14029
  try {
13254
- writeFileSync7(join20(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
14030
+ writeFileSync9(join22(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
13255
14031
  } catch {
13256
14032
  }
13257
14033
  }
@@ -13297,6 +14073,10 @@ var init_status_bar = __esm({
13297
14073
  * own output is suppressed.
13298
14074
  */
13299
14075
  inputStateProvider = null;
14076
+ /** Whether microphone is recording (blinking indicator) */
14077
+ _recording = false;
14078
+ /** Countdown seconds for auto-mode silence timeout (0 = not counting down) */
14079
+ _countdown = 0;
13300
14080
  /**
13301
14081
  * Provide a callback that returns readline's current input state.
13302
14082
  * StatusBar uses this to render typed text and position the cursor
@@ -13305,6 +14085,20 @@ var init_status_bar = __esm({
13305
14085
  setInputStateProvider(provider) {
13306
14086
  this.inputStateProvider = provider;
13307
14087
  }
14088
+ /** Set recording indicator state (blinking red ●) */
14089
+ setRecording(active) {
14090
+ this._recording = active;
14091
+ if (!active)
14092
+ this._countdown = 0;
14093
+ if (this.active)
14094
+ this.renderFooterPreserveCursor();
14095
+ }
14096
+ /** Set countdown seconds for auto-mode silence timeout */
14097
+ setCountdown(seconds) {
14098
+ this._countdown = seconds;
14099
+ if (this.active)
14100
+ this.renderFooterPreserveCursor();
14101
+ }
13308
14102
  /** Context window size to display. Can be updated if model changes. */
13309
14103
  setContextWindowSize(size) {
13310
14104
  this.metrics.contextWindowSize = size;
@@ -13453,7 +14247,13 @@ var init_status_bar = __esm({
13453
14247
  const ctxPct = ctxTotal > 0 ? Math.max(0, Math.min(100, Math.round((1 - ctxUsed / ctxTotal) * 100))) : 100;
13454
14248
  const ctxColor = ctxPct > 50 ? c2.green : ctxPct > 20 ? c2.yellow : c2.red;
13455
14249
  const ctxLabel = c2.blue("Ctx: ") + c2.bold(`${ctxUsed.toLocaleString()}/${ctxTotal.toLocaleString()}`) + ` ${ctxColor(`${ctxPct}%`)}`;
13456
- return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}`;
14250
+ let recordingLabel = "";
14251
+ if (this._recording) {
14252
+ const dot = c2.red("\u25CF");
14253
+ const countdown = this._countdown > 0 ? c2.dim(` ${this._countdown}s`) : "";
14254
+ recordingLabel = pipe + dot + c2.red(" REC") + countdown;
14255
+ }
14256
+ return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${recordingLabel}`;
13457
14257
  }
13458
14258
  // -------------------------------------------------------------------------
13459
14259
  // Private
@@ -13580,23 +14380,22 @@ var init_status_bar = __esm({
13580
14380
  import * as readline2 from "node:readline";
13581
14381
  import { Writable } from "node:stream";
13582
14382
  import { cwd } from "node:process";
13583
- import { resolve as resolve12, join as join21, dirname as dirname3 } from "node:path";
14383
+ import { resolve as resolve13, join as join23, dirname as dirname3, extname as extname6 } from "node:path";
13584
14384
  import { createRequire as createRequire2 } from "node:module";
13585
14385
  import { fileURLToPath } from "node:url";
13586
- import { readFileSync as readFileSync13 } from "node:fs";
13587
- import { existsSync as existsSync15 } from "node:fs";
13588
- import { extname as extname5 } from "node:path";
14386
+ import { readFileSync as readFileSync14 } from "node:fs";
14387
+ import { existsSync as existsSync17 } from "node:fs";
13589
14388
  function getVersion() {
13590
14389
  try {
13591
14390
  const require2 = createRequire2(import.meta.url);
13592
14391
  const thisDir = dirname3(fileURLToPath(import.meta.url));
13593
14392
  const candidates = [
13594
- join21(thisDir, "..", "package.json"),
13595
- join21(thisDir, "..", "..", "package.json"),
13596
- join21(thisDir, "..", "..", "..", "package.json")
14393
+ join23(thisDir, "..", "package.json"),
14394
+ join23(thisDir, "..", "..", "package.json"),
14395
+ join23(thisDir, "..", "..", "..", "package.json")
13597
14396
  ];
13598
14397
  for (const pkgPath of candidates) {
13599
- if (existsSync15(pkgPath)) {
14398
+ if (existsSync17(pkgPath)) {
13600
14399
  const pkg = require2(pkgPath);
13601
14400
  if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
13602
14401
  return pkg.version ?? "0.0.0";
@@ -13673,7 +14472,10 @@ function buildTools(repoRoot, config) {
13673
14472
  ...buildCustomTools(repoRoot),
13674
14473
  // Skill system (AIWG skills — discovery and execution)
13675
14474
  new SkillListTool(repoRoot),
13676
- new SkillExecuteTool(repoRoot)
14475
+ new SkillExecuteTool(repoRoot),
14476
+ // Transcription tools (transcribe-cli / faster-whisper)
14477
+ new TranscribeFileTool(repoRoot),
14478
+ new TranscribeUrlTool(repoRoot)
13677
14479
  ];
13678
14480
  return [
13679
14481
  ...executionTools.map(adaptTool2),
@@ -13926,7 +14728,7 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
13926
14728
  } };
13927
14729
  }
13928
14730
  async function startInteractive(config, repoPath) {
13929
- const repoRoot = resolve12(repoPath ?? cwd());
14731
+ const repoRoot = resolve13(repoPath ?? cwd());
13930
14732
  const isResumed = !!process.env.__OA_RESUMED;
13931
14733
  if (isResumed) {
13932
14734
  delete process.env.__OA_RESUMED;
@@ -14153,7 +14955,7 @@ async function startInteractive(config, repoPath) {
14153
14955
  }
14154
14956
  },
14155
14957
  savePendingTaskState() {
14156
- if (lastSubmittedPrompt) {
14958
+ if (lastSubmittedPrompt && activeTask) {
14157
14959
  savePendingTask(repoRoot, {
14158
14960
  prompt: lastSubmittedPrompt,
14159
14961
  progressSummary: `Manual /update triggered. ${sessionToolCallCount} tool calls completed in last task.`,
@@ -14195,6 +14997,59 @@ async function startInteractive(config, repoPath) {
14195
14997
  },
14196
14998
  isDreaming() {
14197
14999
  return dreamEngine?.isActive ?? false;
15000
+ },
15001
+ // Listen mode (transcribe-cli integration)
15002
+ async listenToggle() {
15003
+ const engine = getListenEngine();
15004
+ if (engine.isActive) {
15005
+ const text = await engine.stop();
15006
+ statusBar.setRecording(false);
15007
+ return text ? `Stopped. Last transcript: "${text}"` : "Stopped listening.";
15008
+ }
15009
+ const available = await engine.isAvailable();
15010
+ if (!available) {
15011
+ return "transcribe-cli not installed. Run: npm i -g transcribe-cli";
15012
+ }
15013
+ engine.on("transcript", (text, isFinal) => {
15014
+ if (engine.currentMode === "confirm") {
15015
+ writeContent(() => renderInfo(`Heard: "${text}" ${c2.dim("(press Enter to submit)")}`));
15016
+ } else if (isFinal) {
15017
+ writeContent(() => renderInfo(`Auto-submitting: "${text}"`));
15018
+ rl.write(text + "\n");
15019
+ } else {
15020
+ writeContent(() => renderInfo(`Hearing: "${text}"`));
15021
+ }
15022
+ });
15023
+ engine.on("recording", (active) => {
15024
+ statusBar.setRecording(active);
15025
+ });
15026
+ engine.on("countdown", (seconds) => {
15027
+ statusBar.setCountdown(seconds);
15028
+ });
15029
+ engine.on("error", (err) => {
15030
+ writeContent(() => renderWarning(`Listen error: ${err.message}`));
15031
+ });
15032
+ engine.on("info", (msg2) => {
15033
+ writeContent(() => renderInfo(msg2));
15034
+ });
15035
+ const msg = await engine.start();
15036
+ return msg;
15037
+ },
15038
+ async listenSetModel(model) {
15039
+ const engine = getListenEngine();
15040
+ engine.setModel(model);
15041
+ return `Whisper model set to: ${model}`;
15042
+ },
15043
+ listenSetMode(mode) {
15044
+ const engine = getListenEngine();
15045
+ engine.setMode(mode);
15046
+ return mode === "auto" ? "Auto mode: transcriptions auto-submit after 3s silence" : "Confirm mode: press Enter to submit transcriptions";
15047
+ },
15048
+ async listenStop() {
15049
+ const engine = getListenEngine();
15050
+ const text = await engine.stop();
15051
+ statusBar.setRecording(false);
15052
+ return text ? `Stopped. Last transcript: "${text}"` : "Stopped listening.";
14198
15053
  }
14199
15054
  };
14200
15055
  showPrompt();
@@ -14242,16 +15097,56 @@ ${c2.dim("Goodbye!")}
14242
15097
  showPrompt();
14243
15098
  return;
14244
15099
  }
15100
+ if (typeof cmdResult === "object" && cmdResult.type === "skill") {
15101
+ const skillPrompt = cmdResult.args ? `<skill-context name="${cmdResult.name}">
15102
+ ${cmdResult.content}
15103
+ </skill-context>
15104
+
15105
+ ARGUMENTS: ${cmdResult.args}` : `<skill-context name="${cmdResult.name}">
15106
+ ${cmdResult.content}
15107
+ </skill-context>
15108
+
15109
+ Execute this skill now. Follow the behavioral guidance above.`;
15110
+ if (!carouselRetired && carousel.isRunning) {
15111
+ carousel.stop();
15112
+ carouselRetired = true;
15113
+ if (statusBar.isActive)
15114
+ statusBar.setScrollRegionTop(1);
15115
+ }
15116
+ writeContent(() => renderUserMessage(`/${cmdResult.name}${cmdResult.args ? " " + cmdResult.args : ""}`));
15117
+ lastSubmittedPrompt = skillPrompt;
15118
+ try {
15119
+ const task = startTask(skillPrompt, currentConfig, repoRoot, voiceEngine, {
15120
+ enabled: streamEnabled,
15121
+ renderer: streamRenderer
15122
+ }, {
15123
+ contextStores,
15124
+ taskMemoryStore: taskMemoryStore ?? void 0,
15125
+ failureStore: failureStore ?? void 0,
15126
+ toolPatternStore: toolPatternStore ?? void 0
15127
+ }, bruteForceEnabled, statusBar);
15128
+ activeTask = task;
15129
+ showPrompt();
15130
+ await task.promise;
15131
+ } catch (err) {
15132
+ const errMsg = err instanceof Error ? err.message : String(err);
15133
+ writeContent(() => renderError(errMsg));
15134
+ }
15135
+ activeTask = null;
15136
+ showPrompt();
15137
+ return;
15138
+ }
14245
15139
  }
14246
15140
  const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
14247
- const isImage = isImagePath(cleanPath) && existsSync15(resolve12(repoRoot, cleanPath));
15141
+ const isImage = isImagePath(cleanPath) && existsSync17(resolve13(repoRoot, cleanPath));
15142
+ const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync17(resolve13(repoRoot, cleanPath));
14248
15143
  if (activeTask) {
14249
15144
  if (isImage) {
14250
15145
  try {
14251
- const imgPath = resolve12(repoRoot, cleanPath);
14252
- const imgBuffer = readFileSync13(imgPath);
15146
+ const imgPath = resolve13(repoRoot, cleanPath);
15147
+ const imgBuffer = readFileSync14(imgPath);
14253
15148
  const base64 = imgBuffer.toString("base64");
14254
- const ext = extname5(cleanPath).toLowerCase();
15149
+ const ext = extname6(cleanPath).toLowerCase();
14255
15150
  const mime = ext === ".png" ? "image/png" : ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/jpeg";
14256
15151
  activeTask.runner.injectImage(base64, mime, `User shared image: ${cleanPath}`);
14257
15152
  writeContent(() => renderUserInterrupt(`[Image: ${cleanPath}]`));
@@ -14259,6 +15154,19 @@ ${c2.dim("Goodbye!")}
14259
15154
  activeTask.runner.injectUserMessage(input);
14260
15155
  writeContent(() => renderUserInterrupt(input));
14261
15156
  }
15157
+ } else if (isMedia) {
15158
+ writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
15159
+ const engine = getListenEngine();
15160
+ const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
15161
+ if (result) {
15162
+ const transcript = `[Transcription of ${cleanPath}]
15163
+ ${result.text}`;
15164
+ activeTask.runner.injectUserMessage(transcript);
15165
+ writeContent(() => renderUserInterrupt(`[Audio: ${cleanPath}] (${result.text.split(" ").length} words)`));
15166
+ } else {
15167
+ activeTask.runner.injectUserMessage(`User dropped audio/video file: ${cleanPath}. Use transcribe_file tool to transcribe it.`);
15168
+ writeContent(() => renderUserInterrupt(`[Media: ${cleanPath}]`));
15169
+ }
14262
15170
  } else {
14263
15171
  activeTask.runner.injectUserMessage(input);
14264
15172
  writeContent(() => renderUserInterrupt(input));
@@ -14274,6 +15182,22 @@ ${c2.dim("Goodbye!")}
14274
15182
  if (isImage && fullInput === input) {
14275
15183
  fullInput = `The user has provided an image file: ${cleanPath}. Read it with image_read and describe what you see. Extract any text with OCR if relevant.`;
14276
15184
  }
15185
+ if (isMedia && fullInput === input) {
15186
+ writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
15187
+ const engine = getListenEngine();
15188
+ const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
15189
+ if (result) {
15190
+ fullInput = `The user has provided an audio/video file: ${cleanPath}.
15191
+
15192
+ Transcription (${result.text.split(" ").length} words, ${result.duration ? result.duration.toFixed(1) + "s" : "unknown duration"}):
15193
+
15194
+ ${result.text}
15195
+
15196
+ Summarize or analyze this transcription as appropriate.`;
15197
+ } else {
15198
+ fullInput = `The user has provided an audio/video file: ${cleanPath}. Use the transcribe_file tool to transcribe it and then summarize the content.`;
15199
+ }
15200
+ }
14277
15201
  if (!carouselRetired && carousel.isRunning) {
14278
15202
  carousel.stop();
14279
15203
  carouselRetired = true;
@@ -14332,19 +15256,6 @@ ${c2.dim("Goodbye!")}
14332
15256
  writeContent(() => renderInfo(`Update available: v${version} \u2192 v${updateInfo.latestVersion}. Installing...`));
14333
15257
  const ok = performSilentUpdate();
14334
15258
  if (ok) {
14335
- if (lastSubmittedPrompt) {
14336
- try {
14337
- savePendingTask(repoRoot, {
14338
- prompt: lastSubmittedPrompt,
14339
- progressSummary: `Task completed ${sessionToolCallCount} tool calls before update. Last task finished normally.`,
14340
- filesModified: sessionFilesTouched,
14341
- bruteForce: bruteForceEnabled,
14342
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
14343
- toolCallCount: sessionToolCallCount
14344
- });
14345
- } catch {
14346
- }
14347
- }
14348
15259
  writeContent(() => renderInfo(`Updated to v${updateInfo.latestVersion}. Reloading...
14349
15260
  `));
14350
15261
  process.env.__OA_RESUMED = "1";
@@ -14384,7 +15295,7 @@ ${c2.dim("(Use /quit to exit)")}
14384
15295
  });
14385
15296
  }
14386
15297
  async function runWithTUI(task, config, repoPath) {
14387
- const repoRoot = resolve12(repoPath ?? cwd());
15298
+ const repoRoot = resolve13(repoPath ?? cwd());
14388
15299
  const needsSetup = isFirstRun() || !await isModelAvailable(config);
14389
15300
  if (needsSetup && config.backendType === "ollama") {
14390
15301
  const setupModel = await runSetupWizard(config);
@@ -14421,6 +15332,7 @@ var init_interactive = __esm({
14421
15332
  "use strict";
14422
15333
  init_dist5();
14423
15334
  init_dist2();
15335
+ init_listen();
14424
15336
  init_updater();
14425
15337
  init_commands();
14426
15338
  init_setup();
@@ -14469,7 +15381,7 @@ import { glob } from "glob";
14469
15381
  import ignore from "ignore";
14470
15382
  import { readFile as readFile9, stat as stat2 } from "node:fs/promises";
14471
15383
  import { createHash } from "node:crypto";
14472
- import { join as join22, relative as relative3, extname as extname6, basename as basename6 } from "node:path";
15384
+ import { join as join24, relative as relative3, extname as extname7, basename as basename7 } from "node:path";
14473
15385
  var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
14474
15386
  var init_codebase_indexer = __esm({
14475
15387
  "packages/indexer/dist/codebase-indexer.js"() {
@@ -14513,7 +15425,7 @@ var init_codebase_indexer = __esm({
14513
15425
  const ig = ignore.default();
14514
15426
  if (this.config.respectGitignore) {
14515
15427
  try {
14516
- const gitignoreContent = await readFile9(join22(this.config.rootDir, ".gitignore"), "utf-8");
15428
+ const gitignoreContent = await readFile9(join24(this.config.rootDir, ".gitignore"), "utf-8");
14517
15429
  ig.add(gitignoreContent);
14518
15430
  } catch {
14519
15431
  }
@@ -14528,14 +15440,14 @@ var init_codebase_indexer = __esm({
14528
15440
  for (const relativePath of files) {
14529
15441
  if (ig.ignores(relativePath))
14530
15442
  continue;
14531
- const fullPath = join22(this.config.rootDir, relativePath);
15443
+ const fullPath = join24(this.config.rootDir, relativePath);
14532
15444
  try {
14533
15445
  const fileStat = await stat2(fullPath);
14534
15446
  if (fileStat.size > this.config.maxFileSize)
14535
15447
  continue;
14536
15448
  const content = await readFile9(fullPath);
14537
15449
  const hash = createHash("sha256").update(content).digest("hex");
14538
- const ext = extname6(relativePath);
15450
+ const ext = extname7(relativePath);
14539
15451
  indexed.push({
14540
15452
  path: fullPath,
14541
15453
  relativePath,
@@ -14551,7 +15463,7 @@ var init_codebase_indexer = __esm({
14551
15463
  }
14552
15464
  buildTree(files) {
14553
15465
  const root = {
14554
- name: basename6(this.config.rootDir),
15466
+ name: basename7(this.config.rootDir),
14555
15467
  path: this.config.rootDir,
14556
15468
  type: "directory",
14557
15469
  children: []
@@ -14574,7 +15486,7 @@ var init_codebase_indexer = __esm({
14574
15486
  if (!child) {
14575
15487
  child = {
14576
15488
  name: part,
14577
- path: join22(current.path, part),
15489
+ path: join24(current.path, part),
14578
15490
  type: "directory",
14579
15491
  children: []
14580
15492
  };
@@ -14648,14 +15560,14 @@ var index_repo_exports = {};
14648
15560
  __export(index_repo_exports, {
14649
15561
  indexRepoCommand: () => indexRepoCommand
14650
15562
  });
14651
- import { resolve as resolve13 } from "node:path";
14652
- import { existsSync as existsSync16, statSync as statSync6 } from "node:fs";
15563
+ import { resolve as resolve14 } from "node:path";
15564
+ import { existsSync as existsSync18, statSync as statSync6 } from "node:fs";
14653
15565
  import { cwd as cwd2 } from "node:process";
14654
15566
  async function indexRepoCommand(opts, _config) {
14655
- const repoRoot = resolve13(opts.repoPath ?? cwd2());
15567
+ const repoRoot = resolve14(opts.repoPath ?? cwd2());
14656
15568
  printHeader("Index Repository");
14657
15569
  printInfo(`Indexing: ${repoRoot}`);
14658
- if (!existsSync16(repoRoot)) {
15570
+ if (!existsSync18(repoRoot)) {
14659
15571
  printError(`Path does not exist: ${repoRoot}`);
14660
15572
  process.exit(1);
14661
15573
  }
@@ -14901,8 +15813,8 @@ var config_exports = {};
14901
15813
  __export(config_exports, {
14902
15814
  configCommand: () => configCommand
14903
15815
  });
14904
- import { join as join23, resolve as resolve14 } from "node:path";
14905
- import { homedir as homedir9 } from "node:os";
15816
+ import { join as join25, resolve as resolve15 } from "node:path";
15817
+ import { homedir as homedir11 } from "node:os";
14906
15818
  import { cwd as cwd3 } from "node:process";
14907
15819
  function coerceForSettings(key, value) {
14908
15820
  if (INT_KEYS.has(key))
@@ -14922,7 +15834,7 @@ async function configCommand(opts, config) {
14922
15834
  return handleShow(opts, config);
14923
15835
  }
14924
15836
  function handleShow(opts, config) {
14925
- const repoRoot = resolve14(opts.repoPath ?? cwd3());
15837
+ const repoRoot = resolve15(opts.repoPath ?? cwd3());
14926
15838
  printHeader("Configuration");
14927
15839
  printSection("Active Settings (merged)");
14928
15840
  printKeyValue("backendUrl", config.backendUrl, 2);
@@ -14954,7 +15866,7 @@ function handleShow(opts, config) {
14954
15866
  }
14955
15867
  }
14956
15868
  printSection("Config File");
14957
- printInfo(`~/.open-agents/config.json (${join23(homedir9(), ".open-agents", "config.json")})`);
15869
+ printInfo(`~/.open-agents/config.json (${join25(homedir11(), ".open-agents", "config.json")})`);
14958
15870
  printSection("Priority Chain");
14959
15871
  printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
14960
15872
  printInfo(" 2. Project .oa/settings.json (--local)");
@@ -14987,13 +15899,13 @@ function handleSet(opts, _config) {
14987
15899
  process.exit(1);
14988
15900
  }
14989
15901
  if (opts.local) {
14990
- const repoRoot = resolve14(opts.repoPath ?? cwd3());
15902
+ const repoRoot = resolve15(opts.repoPath ?? cwd3());
14991
15903
  try {
14992
15904
  initOaDirectory(repoRoot);
14993
15905
  const coerced = coerceForSettings(key, value);
14994
15906
  saveProjectSettings(repoRoot, { [key]: coerced });
14995
15907
  printSuccess(`Project override set: ${key} = ${value}`);
14996
- printInfo(`Saved to ${join23(repoRoot, ".oa", "settings.json")}`);
15908
+ printInfo(`Saved to ${join25(repoRoot, ".oa", "settings.json")}`);
14997
15909
  printInfo("This override applies only when running in this workspace.");
14998
15910
  } catch (err) {
14999
15911
  printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
@@ -15053,7 +15965,7 @@ var serve_exports = {};
15053
15965
  __export(serve_exports, {
15054
15966
  serveCommand: () => serveCommand
15055
15967
  });
15056
- import { spawn as spawn4 } from "node:child_process";
15968
+ import { spawn as spawn6 } from "node:child_process";
15057
15969
  async function serveCommand(opts, config) {
15058
15970
  const backendType = config.backendType;
15059
15971
  if (backendType === "ollama") {
@@ -15145,8 +16057,8 @@ async function serveVllm(opts, config) {
15145
16057
  await runVllmServer(args, opts.verbose ?? false);
15146
16058
  }
15147
16059
  async function runVllmServer(args, verbose) {
15148
- return new Promise((resolve15, reject) => {
15149
- const child = spawn4("python", args, {
16060
+ return new Promise((resolve16, reject) => {
16061
+ const child = spawn6("python", args, {
15150
16062
  stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
15151
16063
  env: { ...process.env }
15152
16064
  });
@@ -15180,10 +16092,10 @@ async function runVllmServer(args, verbose) {
15180
16092
  child.once("exit", (code, signal) => {
15181
16093
  if (signal) {
15182
16094
  printInfo(`vLLM server stopped by signal ${signal}`);
15183
- resolve15();
16095
+ resolve16();
15184
16096
  } else if (code === 0) {
15185
16097
  printSuccess("vLLM server exited cleanly");
15186
- resolve15();
16098
+ resolve16();
15187
16099
  } else {
15188
16100
  printError(`vLLM server exited with code ${code}`);
15189
16101
  reject(new Error(`vLLM exited with code ${code}`));
@@ -15211,8 +16123,8 @@ __export(eval_exports, {
15211
16123
  evalCommand: () => evalCommand
15212
16124
  });
15213
16125
  import { tmpdir as tmpdir3 } from "node:os";
15214
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "node:fs";
15215
- import { join as join24 } from "node:path";
16126
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
16127
+ import { join as join26 } from "node:path";
15216
16128
  async function evalCommand(opts, config) {
15217
16129
  const suiteName = opts.suite ?? "basic";
15218
16130
  const suite = SUITES[suiteName];
@@ -15333,9 +16245,9 @@ async function evalCommand(opts, config) {
15333
16245
  process.exit(failed > 0 ? 1 : 0);
15334
16246
  }
15335
16247
  function createTempEvalRepo() {
15336
- const dir = join24(tmpdir3(), `open-agents-eval-${Date.now()}`);
15337
- mkdirSync9(dir, { recursive: true });
15338
- writeFileSync8(join24(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
16248
+ const dir = join26(tmpdir3(), `open-agents-eval-${Date.now()}`);
16249
+ mkdirSync11(dir, { recursive: true });
16250
+ writeFileSync10(join26(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
15339
16251
  return dir;
15340
16252
  }
15341
16253
  var BASIC_SUITE, FULL_SUITE, SUITES;
@@ -15395,7 +16307,7 @@ init_updater();
15395
16307
  import { parseArgs as nodeParseArgs2 } from "node:util";
15396
16308
  import { createRequire as createRequire3 } from "node:module";
15397
16309
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15398
- import { dirname as dirname4, join as join25 } from "node:path";
16310
+ import { dirname as dirname4, join as join27 } from "node:path";
15399
16311
 
15400
16312
  // packages/cli/dist/cli.js
15401
16313
  import { createInterface } from "node:readline";
@@ -15502,7 +16414,7 @@ init_output();
15502
16414
  function getVersion2() {
15503
16415
  try {
15504
16416
  const require2 = createRequire3(import.meta.url);
15505
- const pkgPath = join25(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
16417
+ const pkgPath = join27(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
15506
16418
  const pkg = require2(pkgPath);
15507
16419
  return pkg.version;
15508
16420
  } catch {