open-agents-ai 0.76.0 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -32
- package/dist/launcher.cjs +81 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9550,7 +9550,18 @@ function installCronJob(task, workingDir) {
|
|
|
9550
9550
|
const oaBin = findOaBinary();
|
|
9551
9551
|
const logDir = resolve21(workingDir, ".oa", "scheduled", "logs");
|
|
9552
9552
|
const logFile = join23(logDir, `${task.id}.log`);
|
|
9553
|
-
const
|
|
9553
|
+
const storeFile = resolve21(workingDir, ".oa", "scheduled", "tasks.json");
|
|
9554
|
+
const taskEscaped = task.task.replace(/'/g, "'\\''");
|
|
9555
|
+
const taskId = task.id;
|
|
9556
|
+
const wrapperCmd = [
|
|
9557
|
+
`cd ${JSON.stringify(workingDir)}`,
|
|
9558
|
+
`mkdir -p ${JSON.stringify(logDir)}`,
|
|
9559
|
+
// Run the task and capture exit code
|
|
9560
|
+
`${oaBin} '${taskEscaped}' >> ${JSON.stringify(logFile)} 2>&1; _oa_exit=$?`,
|
|
9561
|
+
// Update store: increment runCount, set lastRun, handle oneShot/maxRuns
|
|
9562
|
+
`node -e "const fs=require('fs'),p=${JSON.stringify(storeFile)};try{const s=JSON.parse(fs.readFileSync(p,'utf8'));const t=s.tasks.find(x=>x.id==='${taskId}');if(t){t.runCount=(t.runCount||0)+1;t.lastRun=new Date().toISOString();if(t.oneShot)t.enabled=false;if(t.maxRuns&&t.runCount>=t.maxRuns)t.enabled=false;s.updatedAt=new Date().toISOString();fs.writeFileSync(p,JSON.stringify(s,null,2));}}catch(e){}" 2>/dev/null`
|
|
9563
|
+
].join("; ");
|
|
9564
|
+
const cronLine = `${task.schedule} ${wrapperCmd} ${CRON_MARKER}${task.id}`;
|
|
9554
9565
|
const filtered = lines.filter((l) => !l.includes(`${CRON_MARKER}${task.id}`));
|
|
9555
9566
|
filtered.push(cronLine);
|
|
9556
9567
|
writeCrontab(filtered);
|
|
@@ -10815,8 +10826,21 @@ function installCronAgentJob(job, workingDir) {
|
|
|
10815
10826
|
const oaBin = findOaBinary2();
|
|
10816
10827
|
const logDir = resolve25(workingDir, ".oa", "cron-agents", "logs");
|
|
10817
10828
|
const logFile = join27(logDir, `${job.id}.log`);
|
|
10829
|
+
const storeFile = resolve25(workingDir, ".oa", "cron-agents", "store.json");
|
|
10818
10830
|
const taskEscaped = job.task.replace(/'/g, "'\\''");
|
|
10819
|
-
const
|
|
10831
|
+
const jobId = job.id;
|
|
10832
|
+
const wrapperCmd = [
|
|
10833
|
+
`cd ${JSON.stringify(workingDir)}`,
|
|
10834
|
+
`mkdir -p ${JSON.stringify(logDir)}`,
|
|
10835
|
+
`_oa_start=$(date +%s%3N 2>/dev/null || date +%s)`,
|
|
10836
|
+
// Run the task and capture exit code
|
|
10837
|
+
`${oaBin} '${taskEscaped}' >> ${JSON.stringify(logFile)} 2>&1; _oa_exit=$?`,
|
|
10838
|
+
`_oa_end=$(date +%s%3N 2>/dev/null || date +%s)`,
|
|
10839
|
+
// Update store: increment runCount, set lastRunAt, append to history
|
|
10840
|
+
// Pass shell vars as node CLI args: node -e "..." exitCode startMs endMs
|
|
10841
|
+
`node -e "const fs=require('fs'),p=${JSON.stringify(storeFile)};const[,,ex,st,en]=process.argv;try{const s=JSON.parse(fs.readFileSync(p,'utf8'));const j=s.jobs.find(x=>x.id==='${jobId}');if(j){j.runCount=(j.runCount||0)+1;j.lastRunAt=new Date().toISOString();const dur=Math.max(0,(+en||0)-(+st||0));j.history=(j.history||[]).concat({runAt:j.lastRunAt,exitCode:+(ex||0),outputSummary:'cron execution',durationMs:dur});if(j.history.length>20)j.history=j.history.slice(-20);if(j.maxRuns>0&&j.runCount>=j.maxRuns)j.status='completed';s.updatedAt=new Date().toISOString();fs.writeFileSync(p,JSON.stringify(s,null,2));}}catch(e){}" $_oa_exit $_oa_start $_oa_end 2>/dev/null`
|
|
10842
|
+
].join("; ");
|
|
10843
|
+
const cronLine = `${job.schedule} ${wrapperCmd} ${CRON_AGENT_MARKER}${job.id}`;
|
|
10820
10844
|
const filtered = lines.filter((l) => !l.includes(`${CRON_AGENT_MARKER}${job.id}`));
|
|
10821
10845
|
filtered.push(cronLine);
|
|
10822
10846
|
writeCrontab2(filtered);
|
|
@@ -30568,18 +30592,31 @@ var init_voice = __esm({
|
|
|
30568
30592
|
return `Voice model set to ${VOICE_MODELS[key].label}. Enable with /voice.`;
|
|
30569
30593
|
}
|
|
30570
30594
|
/**
|
|
30571
|
-
* Speak text asynchronously (non-blocking).
|
|
30595
|
+
* Speak text asynchronously (non-blocking) at full volume.
|
|
30572
30596
|
* Long text is chunked on sentence/line boundaries for reliable TTS.
|
|
30573
30597
|
* Chunks are queued FIFO and played back-to-back without cutoff.
|
|
30574
30598
|
*/
|
|
30575
30599
|
speak(text) {
|
|
30600
|
+
this.enqueueSpeech(text, 1, 1);
|
|
30601
|
+
}
|
|
30602
|
+
/**
|
|
30603
|
+
* Speak text at reduced volume with slight pitch shift to indicate
|
|
30604
|
+
* subordinate/secondary output (sub-agent activity, tool narration, etc.).
|
|
30605
|
+
* Volume: 55% of primary. Pitch: shifted down slightly (0.92x).
|
|
30606
|
+
*/
|
|
30607
|
+
speakSubordinate(text) {
|
|
30608
|
+
this.enqueueSpeech(text, 0.55, 0.92);
|
|
30609
|
+
}
|
|
30610
|
+
enqueueSpeech(text, volume, pitchFactor) {
|
|
30576
30611
|
if (!this.enabled || !this.ready)
|
|
30577
30612
|
return;
|
|
30578
30613
|
const chunks = this.chunkText(text);
|
|
30579
30614
|
if (this.speakQueue.length >= 30) {
|
|
30580
30615
|
this.speakQueue.length = 0;
|
|
30581
30616
|
}
|
|
30582
|
-
|
|
30617
|
+
for (const chunk of chunks) {
|
|
30618
|
+
this.speakQueue.push({ text: chunk, volume, pitchFactor });
|
|
30619
|
+
}
|
|
30583
30620
|
if (!this.speaking) {
|
|
30584
30621
|
this.drainQueue().catch(() => {
|
|
30585
30622
|
});
|
|
@@ -30704,33 +30741,98 @@ var init_voice = __esm({
|
|
|
30704
30741
|
// -------------------------------------------------------------------------
|
|
30705
30742
|
/**
|
|
30706
30743
|
* Drain the speak queue FIFO — each item is synthesized and played to
|
|
30707
|
-
* completion before the next starts.
|
|
30708
|
-
*
|
|
30744
|
+
* completion before the next starts. A brief silence gap (250ms) is
|
|
30745
|
+
* inserted between queued items for natural pacing.
|
|
30709
30746
|
*/
|
|
30710
30747
|
async drainQueue() {
|
|
30711
30748
|
this.speaking = true;
|
|
30749
|
+
let isFirst = true;
|
|
30712
30750
|
while (this.speakQueue.length > 0) {
|
|
30713
|
-
const
|
|
30751
|
+
const item = this.speakQueue.shift();
|
|
30752
|
+
if (!isFirst) {
|
|
30753
|
+
await this.sleep(250);
|
|
30754
|
+
}
|
|
30755
|
+
isFirst = false;
|
|
30714
30756
|
try {
|
|
30715
|
-
await this.synthesizeAndPlay(text);
|
|
30757
|
+
await this.synthesizeAndPlay(item.text, item.volume, item.pitchFactor);
|
|
30716
30758
|
} catch {
|
|
30717
30759
|
}
|
|
30718
30760
|
}
|
|
30719
30761
|
this.speaking = false;
|
|
30720
30762
|
}
|
|
30763
|
+
sleep(ms) {
|
|
30764
|
+
return new Promise((resolve31) => setTimeout(resolve31, ms));
|
|
30765
|
+
}
|
|
30721
30766
|
// -------------------------------------------------------------------------
|
|
30722
30767
|
// Synthesis pipeline
|
|
30723
30768
|
// -------------------------------------------------------------------------
|
|
30724
|
-
async synthesizeAndPlay(text) {
|
|
30769
|
+
async synthesizeAndPlay(text, volume = 1, pitchFactor = 1) {
|
|
30725
30770
|
if (!this.session || !this.config || !this.ort)
|
|
30726
30771
|
return;
|
|
30727
30772
|
const cleaned = text.replace(/\*/g, "");
|
|
30728
30773
|
if (!cleaned.trim())
|
|
30729
30774
|
return;
|
|
30730
|
-
const
|
|
30775
|
+
const sentences = cleaned.split(/(?<=[.!?])\s+/).filter((s) => s.trim());
|
|
30776
|
+
const sampleRate = this.config.audio.sample_rate;
|
|
30777
|
+
const sentenceSilenceSamples = Math.round(sampleRate * 0.18);
|
|
30778
|
+
const sentenceSilence = new Float32Array(sentenceSilenceSamples);
|
|
30779
|
+
const allSegments = [];
|
|
30780
|
+
for (let si = 0; si < sentences.length; si++) {
|
|
30781
|
+
const sentence = sentences[si].trim();
|
|
30782
|
+
if (!sentence)
|
|
30783
|
+
continue;
|
|
30784
|
+
const audioData2 = await this.synthesizeRaw(sentence);
|
|
30785
|
+
if (!audioData2 || audioData2.length === 0)
|
|
30786
|
+
continue;
|
|
30787
|
+
allSegments.push(audioData2);
|
|
30788
|
+
if (si < sentences.length - 1) {
|
|
30789
|
+
allSegments.push(sentenceSilence);
|
|
30790
|
+
}
|
|
30791
|
+
}
|
|
30792
|
+
if (allSegments.length === 0)
|
|
30793
|
+
return;
|
|
30794
|
+
const totalLen = allSegments.reduce((acc, seg) => acc + seg.length, 0);
|
|
30795
|
+
let audioData = new Float32Array(totalLen);
|
|
30796
|
+
let offset = 0;
|
|
30797
|
+
for (const seg of allSegments) {
|
|
30798
|
+
audioData.set(seg, offset);
|
|
30799
|
+
offset += seg.length;
|
|
30800
|
+
}
|
|
30801
|
+
if (volume !== 1) {
|
|
30802
|
+
for (let i = 0; i < audioData.length; i++) {
|
|
30803
|
+
audioData[i] = audioData[i] * volume;
|
|
30804
|
+
}
|
|
30805
|
+
}
|
|
30806
|
+
if (pitchFactor !== 1) {
|
|
30807
|
+
audioData = this.resamplePitch(audioData, pitchFactor);
|
|
30808
|
+
}
|
|
30809
|
+
if (this.onPCMOutput) {
|
|
30810
|
+
const int16 = new Int16Array(audioData.length);
|
|
30811
|
+
for (let i = 0; i < audioData.length; i++) {
|
|
30812
|
+
const s = Math.max(-1, Math.min(1, audioData[i]));
|
|
30813
|
+
int16[i] = s < 0 ? s * 32768 : s * 32767;
|
|
30814
|
+
}
|
|
30815
|
+
this.onPCMOutput(Buffer.from(int16.buffer), sampleRate);
|
|
30816
|
+
}
|
|
30817
|
+
const wavPath = join40(tmpdir6(), `oa-voice-${Date.now()}.wav`);
|
|
30818
|
+
this.writeWav(audioData, sampleRate, wavPath);
|
|
30819
|
+
await this.playWav(wavPath);
|
|
30820
|
+
try {
|
|
30821
|
+
unlinkSync4(wavPath);
|
|
30822
|
+
} catch {
|
|
30823
|
+
}
|
|
30824
|
+
}
|
|
30825
|
+
/**
|
|
30826
|
+
* Synthesize text to raw Float32 audio samples via ONNX inference.
|
|
30827
|
+
* Returns null if synthesis fails or produces no audio.
|
|
30828
|
+
*/
|
|
30829
|
+
async synthesizeRaw(text) {
|
|
30830
|
+
if (!this.session || !this.config || !this.ort)
|
|
30831
|
+
return null;
|
|
30832
|
+
const textPhonemes = await this.textToPhonemes(text);
|
|
30731
30833
|
const phonemeIds = this.phonemesToIds(textPhonemes);
|
|
30732
30834
|
if (phonemeIds.length === 0)
|
|
30733
|
-
return;
|
|
30835
|
+
return null;
|
|
30734
30836
|
const inputLength = phonemeIds.length;
|
|
30735
30837
|
const inputTensor = new this.ort.Tensor("int64", BigInt64Array.from(phonemeIds.map((id) => BigInt(id))), [1, inputLength]);
|
|
30736
30838
|
const lengthTensor = new this.ort.Tensor("int64", BigInt64Array.from([BigInt(inputLength)]), [1]);
|
|
@@ -30745,23 +30847,28 @@ var init_voice = __esm({
|
|
|
30745
30847
|
}
|
|
30746
30848
|
const result = await this.session.run(feeds);
|
|
30747
30849
|
const audioData = result["output"].data;
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
|
|
30751
|
-
|
|
30752
|
-
|
|
30753
|
-
|
|
30754
|
-
|
|
30755
|
-
|
|
30756
|
-
|
|
30757
|
-
|
|
30758
|
-
const
|
|
30759
|
-
|
|
30760
|
-
|
|
30761
|
-
|
|
30762
|
-
|
|
30763
|
-
|
|
30764
|
-
|
|
30850
|
+
return audioData.length > 0 ? audioData : null;
|
|
30851
|
+
}
|
|
30852
|
+
/**
|
|
30853
|
+
* Pitch-shift audio via linear-interpolation resampling.
|
|
30854
|
+
* Factor > 1.0 = higher pitch (shorter duration).
|
|
30855
|
+
* Factor < 1.0 = lower pitch (longer duration).
|
|
30856
|
+
* Duration is preserved by adjusting the output length accordingly —
|
|
30857
|
+
* we resample to change pitch, then the WAV sample rate stays constant.
|
|
30858
|
+
*/
|
|
30859
|
+
resamplePitch(samples, factor) {
|
|
30860
|
+
const newLen = Math.round(samples.length / factor);
|
|
30861
|
+
if (newLen <= 0)
|
|
30862
|
+
return samples;
|
|
30863
|
+
const out = new Float32Array(newLen);
|
|
30864
|
+
for (let i = 0; i < newLen; i++) {
|
|
30865
|
+
const srcIdx = i * factor;
|
|
30866
|
+
const lo = Math.floor(srcIdx);
|
|
30867
|
+
const hi = Math.min(lo + 1, samples.length - 1);
|
|
30868
|
+
const frac = srcIdx - lo;
|
|
30869
|
+
out[i] = samples[lo] * (1 - frac) + samples[hi] * frac;
|
|
30870
|
+
}
|
|
30871
|
+
return out;
|
|
30765
30872
|
}
|
|
30766
30873
|
// -------------------------------------------------------------------------
|
|
30767
30874
|
// Phonemization
|
|
@@ -35745,8 +35852,12 @@ with summary "no_reply" to silently skip without responding.
|
|
|
35745
35852
|
}
|
|
35746
35853
|
const existing = this.subAgents.get(msg.chatId);
|
|
35747
35854
|
if (existing && !existing.aborted) {
|
|
35748
|
-
existing.runner
|
|
35749
|
-
|
|
35855
|
+
if (existing.runner) {
|
|
35856
|
+
existing.runner.injectUserMessage(msg.text);
|
|
35857
|
+
this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, "mid-conversation steering injected"));
|
|
35858
|
+
} else {
|
|
35859
|
+
this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, "queued (sub-agent still starting)"));
|
|
35860
|
+
}
|
|
35750
35861
|
return;
|
|
35751
35862
|
}
|
|
35752
35863
|
const subAgent = {
|
|
@@ -38168,7 +38279,7 @@ ${entry.fullContent}`
|
|
|
38168
38279
|
const emoCtx = emoState ? { valence: emoState.valence, arousal: emoState.arousal, label: emoState.label, emoji: emoState.emoji } : void 0;
|
|
38169
38280
|
const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {}, vLevel, emoCtx);
|
|
38170
38281
|
renderVoiceText(desc);
|
|
38171
|
-
voice.
|
|
38282
|
+
voice.speakSubordinate(desc);
|
|
38172
38283
|
}
|
|
38173
38284
|
renderToolCallStart(event.toolName ?? "unknown", event.toolArgs ?? {}, config.verbose);
|
|
38174
38285
|
});
|
|
@@ -38206,7 +38317,7 @@ ${entry.fullContent}`
|
|
|
38206
38317
|
const desc = describeToolResult(event.toolName ?? "unknown", event.success ?? false, vLevel, event.content ?? void 0, emoCtx2);
|
|
38207
38318
|
if (desc) {
|
|
38208
38319
|
renderVoiceText(desc);
|
|
38209
|
-
voice.
|
|
38320
|
+
voice.speakSubordinate(desc);
|
|
38210
38321
|
}
|
|
38211
38322
|
}
|
|
38212
38323
|
});
|
package/dist/launcher.cjs
CHANGED
|
@@ -301,7 +301,87 @@ if (nodeVersion < 22) {
|
|
|
301
301
|
});
|
|
302
302
|
});
|
|
303
303
|
} else {
|
|
304
|
-
// Node >= 22 —
|
|
304
|
+
// Node >= 22 — check sub-dependencies before loading the ESM bundle
|
|
305
|
+
var _path = require("path");
|
|
306
|
+
var _fs = require("fs");
|
|
307
|
+
var _cp = require("child_process");
|
|
308
|
+
|
|
309
|
+
// Resolve the package root (where package.json lives)
|
|
310
|
+
var _pkgDir = _path.dirname(__dirname.endsWith("dist") ? _path.dirname(__dirname) : __dirname);
|
|
311
|
+
// If we're in .../lib/node_modules/open-agents-ai/dist/, go up to the package root
|
|
312
|
+
var _pkgJson = _path.join(_pkgDir, "package.json");
|
|
313
|
+
if (!_fs.existsSync(_pkgJson)) {
|
|
314
|
+
_pkgDir = _path.resolve(__dirname, "..");
|
|
315
|
+
_pkgJson = _path.join(_pkgDir, "package.json");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Dependencies to verify — both required and optional
|
|
319
|
+
var _criticalDeps = ["better-sqlite3", "ws", "zod", "glob", "ignore"];
|
|
320
|
+
var _optionalDeps = ["open-agents-nexus", "nats.ws", "moondream", "aiwg"];
|
|
321
|
+
var _missing = [];
|
|
322
|
+
var _missingOptional = [];
|
|
323
|
+
|
|
324
|
+
function _canResolve(name) {
|
|
325
|
+
try {
|
|
326
|
+
// Check if the module exists in the package's node_modules
|
|
327
|
+
var modDir = _path.join(_pkgDir, "node_modules", name);
|
|
328
|
+
if (_fs.existsSync(modDir)) return true;
|
|
329
|
+
// Also try require.resolve from the package dir
|
|
330
|
+
require.resolve(name, { paths: [_pkgDir] });
|
|
331
|
+
return true;
|
|
332
|
+
} catch (e) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for (var _i = 0; _i < _criticalDeps.length; _i++) {
|
|
338
|
+
if (!_canResolve(_criticalDeps[_i])) _missing.push(_criticalDeps[_i]);
|
|
339
|
+
}
|
|
340
|
+
for (var _j = 0; _j < _optionalDeps.length; _j++) {
|
|
341
|
+
if (!_canResolve(_optionalDeps[_j])) _missingOptional.push(_optionalDeps[_j]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
var _allMissing = _missing.concat(_missingOptional);
|
|
345
|
+
|
|
346
|
+
if (_allMissing.length > 0) {
|
|
347
|
+
var _label = _missing.length > 0 ? "required" : "optional";
|
|
348
|
+
console.log(" Installing " + _allMissing.length + " missing " + _label + " sub-dependencies...");
|
|
349
|
+
console.log(" Missing: " + _allMissing.join(", "));
|
|
350
|
+
console.log("");
|
|
351
|
+
|
|
352
|
+
// Run npm install in the package directory to restore all deps
|
|
353
|
+
try {
|
|
354
|
+
_cp.execSync("npm install --no-audit --no-fund", {
|
|
355
|
+
cwd: _pkgDir,
|
|
356
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
357
|
+
timeout: 120000,
|
|
358
|
+
env: Object.assign({}, process.env, { OA_SELF_UPGRADE: "1" }),
|
|
359
|
+
});
|
|
360
|
+
console.log(" Sub-dependencies installed.");
|
|
361
|
+
console.log("");
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// If full install fails, try installing each missing dep individually
|
|
364
|
+
for (var _k = 0; _k < _allMissing.length; _k++) {
|
|
365
|
+
var _dep = _allMissing[_k];
|
|
366
|
+
try {
|
|
367
|
+
var _isOpt = _missingOptional.indexOf(_dep) !== -1;
|
|
368
|
+
console.log(" Installing " + _dep + (_isOpt ? " (optional)" : "") + "...");
|
|
369
|
+
_cp.execSync("npm install " + _dep + " --no-audit --no-fund" + (_isOpt ? " --save-optional" : ""), {
|
|
370
|
+
cwd: _pkgDir,
|
|
371
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
372
|
+
timeout: 60000,
|
|
373
|
+
});
|
|
374
|
+
} catch (e2) {
|
|
375
|
+
if (_missing.indexOf(_dep) !== -1) {
|
|
376
|
+
console.error(" WARNING: Failed to install required dep: " + _dep);
|
|
377
|
+
}
|
|
378
|
+
// Optional deps failing is fine
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Load the ESM bundle
|
|
305
385
|
import("./index.js").catch(function(err) {
|
|
306
386
|
console.error("Failed to load open-agents:", err.message || err);
|
|
307
387
|
process.exit(1);
|
package/package.json
CHANGED