open-agents-ai 0.77.0 → 0.79.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 +161 -36
- package/package.json +2 -2
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);
|
|
@@ -11409,7 +11433,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11409
11433
|
];
|
|
11410
11434
|
NexusTool = class {
|
|
11411
11435
|
name = "nexus";
|
|
11412
|
-
description = "Decentralized agent-to-agent communication via open-agents-nexus v1.
|
|
11436
|
+
description = "Decentralized agent-to-agent communication via open-agents-nexus v1.3.0. Spawns a background Node.js process with real network sockets for libp2p + NATS P2P mesh. Simple flow: connect \u2192 join room \u2192 send messages. Auto-installs nexus if needed. Also supports direct peer invoke (streaming inference), IPFS content storage, direct messages, peer discovery, x402 micropayments, and inference proofs. Onboarding: curl openagents.nexus/llms.txt for full instructions, /.well-known/agent.json for machine-readable manifest.";
|
|
11413
11437
|
parameters = {
|
|
11414
11438
|
type: "object",
|
|
11415
11439
|
properties: {
|
|
@@ -30079,6 +30103,10 @@ function modelOnnxPath(id) {
|
|
|
30079
30103
|
function modelConfigPath(id) {
|
|
30080
30104
|
return join40(modelDir(id), "config.json");
|
|
30081
30105
|
}
|
|
30106
|
+
function emotionToPitchBias(emotion) {
|
|
30107
|
+
const raw = emotion.valence * 0.6 + (emotion.arousal - 0.5) * 0.4;
|
|
30108
|
+
return Math.max(-0.1, Math.min(0.1, raw * 0.1));
|
|
30109
|
+
}
|
|
30082
30110
|
function resetNarrationContext() {
|
|
30083
30111
|
narration.toolCount = 0;
|
|
30084
30112
|
narration.toolCounts = {};
|
|
@@ -30568,18 +30596,37 @@ var init_voice = __esm({
|
|
|
30568
30596
|
return `Voice model set to ${VOICE_MODELS[key].label}. Enable with /voice.`;
|
|
30569
30597
|
}
|
|
30570
30598
|
/**
|
|
30571
|
-
* Speak text asynchronously (non-blocking).
|
|
30599
|
+
* Speak text asynchronously (non-blocking) at full volume.
|
|
30572
30600
|
* Long text is chunked on sentence/line boundaries for reliable TTS.
|
|
30573
30601
|
* Chunks are queued FIFO and played back-to-back without cutoff.
|
|
30602
|
+
* If emotion context is provided, pitch is biased by valence/arousal:
|
|
30603
|
+
* excited (high valence + high arousal) → higher pitch
|
|
30604
|
+
* dejected (low valence + low arousal) → lower pitch
|
|
30605
|
+
*/
|
|
30606
|
+
speak(text, emotion) {
|
|
30607
|
+
const pitchBias = emotion ? emotionToPitchBias(emotion) : 0;
|
|
30608
|
+
this.enqueueSpeech(text, 1, 1 + pitchBias);
|
|
30609
|
+
}
|
|
30610
|
+
/**
|
|
30611
|
+
* Speak text at reduced volume with pitch shift to indicate
|
|
30612
|
+
* subordinate/secondary output (sub-agent activity, tool narration, etc.).
|
|
30613
|
+
* Volume: 55% of primary. Base pitch: 0.92x (lower).
|
|
30614
|
+
* Emotion further modulates pitch on top of the subordinate base.
|
|
30574
30615
|
*/
|
|
30575
|
-
|
|
30616
|
+
speakSubordinate(text, emotion) {
|
|
30617
|
+
const pitchBias = emotion ? emotionToPitchBias(emotion) : 0;
|
|
30618
|
+
this.enqueueSpeech(text, 0.55, 0.92 + pitchBias);
|
|
30619
|
+
}
|
|
30620
|
+
enqueueSpeech(text, volume, pitchFactor) {
|
|
30576
30621
|
if (!this.enabled || !this.ready)
|
|
30577
30622
|
return;
|
|
30578
30623
|
const chunks = this.chunkText(text);
|
|
30579
30624
|
if (this.speakQueue.length >= 30) {
|
|
30580
30625
|
this.speakQueue.length = 0;
|
|
30581
30626
|
}
|
|
30582
|
-
|
|
30627
|
+
for (const chunk of chunks) {
|
|
30628
|
+
this.speakQueue.push({ text: chunk, volume, pitchFactor });
|
|
30629
|
+
}
|
|
30583
30630
|
if (!this.speaking) {
|
|
30584
30631
|
this.drainQueue().catch(() => {
|
|
30585
30632
|
});
|
|
@@ -30704,33 +30751,98 @@ var init_voice = __esm({
|
|
|
30704
30751
|
// -------------------------------------------------------------------------
|
|
30705
30752
|
/**
|
|
30706
30753
|
* Drain the speak queue FIFO — each item is synthesized and played to
|
|
30707
|
-
* completion before the next starts.
|
|
30708
|
-
*
|
|
30754
|
+
* completion before the next starts. A brief silence gap (250ms) is
|
|
30755
|
+
* inserted between queued items for natural pacing.
|
|
30709
30756
|
*/
|
|
30710
30757
|
async drainQueue() {
|
|
30711
30758
|
this.speaking = true;
|
|
30759
|
+
let isFirst = true;
|
|
30712
30760
|
while (this.speakQueue.length > 0) {
|
|
30713
|
-
const
|
|
30761
|
+
const item = this.speakQueue.shift();
|
|
30762
|
+
if (!isFirst) {
|
|
30763
|
+
await this.sleep(250);
|
|
30764
|
+
}
|
|
30765
|
+
isFirst = false;
|
|
30714
30766
|
try {
|
|
30715
|
-
await this.synthesizeAndPlay(text);
|
|
30767
|
+
await this.synthesizeAndPlay(item.text, item.volume, item.pitchFactor);
|
|
30716
30768
|
} catch {
|
|
30717
30769
|
}
|
|
30718
30770
|
}
|
|
30719
30771
|
this.speaking = false;
|
|
30720
30772
|
}
|
|
30773
|
+
sleep(ms) {
|
|
30774
|
+
return new Promise((resolve31) => setTimeout(resolve31, ms));
|
|
30775
|
+
}
|
|
30721
30776
|
// -------------------------------------------------------------------------
|
|
30722
30777
|
// Synthesis pipeline
|
|
30723
30778
|
// -------------------------------------------------------------------------
|
|
30724
|
-
async synthesizeAndPlay(text) {
|
|
30779
|
+
async synthesizeAndPlay(text, volume = 1, pitchFactor = 1) {
|
|
30725
30780
|
if (!this.session || !this.config || !this.ort)
|
|
30726
30781
|
return;
|
|
30727
30782
|
const cleaned = text.replace(/\*/g, "");
|
|
30728
30783
|
if (!cleaned.trim())
|
|
30729
30784
|
return;
|
|
30730
|
-
const
|
|
30785
|
+
const sentences = cleaned.split(/(?<=[.!?])\s+/).filter((s) => s.trim());
|
|
30786
|
+
const sampleRate = this.config.audio.sample_rate;
|
|
30787
|
+
const sentenceSilenceSamples = Math.round(sampleRate * 0.18);
|
|
30788
|
+
const sentenceSilence = new Float32Array(sentenceSilenceSamples);
|
|
30789
|
+
const allSegments = [];
|
|
30790
|
+
for (let si = 0; si < sentences.length; si++) {
|
|
30791
|
+
const sentence = sentences[si].trim();
|
|
30792
|
+
if (!sentence)
|
|
30793
|
+
continue;
|
|
30794
|
+
const audioData2 = await this.synthesizeRaw(sentence);
|
|
30795
|
+
if (!audioData2 || audioData2.length === 0)
|
|
30796
|
+
continue;
|
|
30797
|
+
allSegments.push(audioData2);
|
|
30798
|
+
if (si < sentences.length - 1) {
|
|
30799
|
+
allSegments.push(sentenceSilence);
|
|
30800
|
+
}
|
|
30801
|
+
}
|
|
30802
|
+
if (allSegments.length === 0)
|
|
30803
|
+
return;
|
|
30804
|
+
const totalLen = allSegments.reduce((acc, seg) => acc + seg.length, 0);
|
|
30805
|
+
let audioData = new Float32Array(totalLen);
|
|
30806
|
+
let offset = 0;
|
|
30807
|
+
for (const seg of allSegments) {
|
|
30808
|
+
audioData.set(seg, offset);
|
|
30809
|
+
offset += seg.length;
|
|
30810
|
+
}
|
|
30811
|
+
if (volume !== 1) {
|
|
30812
|
+
for (let i = 0; i < audioData.length; i++) {
|
|
30813
|
+
audioData[i] = audioData[i] * volume;
|
|
30814
|
+
}
|
|
30815
|
+
}
|
|
30816
|
+
if (pitchFactor !== 1) {
|
|
30817
|
+
audioData = this.resamplePitch(audioData, pitchFactor);
|
|
30818
|
+
}
|
|
30819
|
+
if (this.onPCMOutput) {
|
|
30820
|
+
const int16 = new Int16Array(audioData.length);
|
|
30821
|
+
for (let i = 0; i < audioData.length; i++) {
|
|
30822
|
+
const s = Math.max(-1, Math.min(1, audioData[i]));
|
|
30823
|
+
int16[i] = s < 0 ? s * 32768 : s * 32767;
|
|
30824
|
+
}
|
|
30825
|
+
this.onPCMOutput(Buffer.from(int16.buffer), sampleRate);
|
|
30826
|
+
}
|
|
30827
|
+
const wavPath = join40(tmpdir6(), `oa-voice-${Date.now()}.wav`);
|
|
30828
|
+
this.writeWav(audioData, sampleRate, wavPath);
|
|
30829
|
+
await this.playWav(wavPath);
|
|
30830
|
+
try {
|
|
30831
|
+
unlinkSync4(wavPath);
|
|
30832
|
+
} catch {
|
|
30833
|
+
}
|
|
30834
|
+
}
|
|
30835
|
+
/**
|
|
30836
|
+
* Synthesize text to raw Float32 audio samples via ONNX inference.
|
|
30837
|
+
* Returns null if synthesis fails or produces no audio.
|
|
30838
|
+
*/
|
|
30839
|
+
async synthesizeRaw(text) {
|
|
30840
|
+
if (!this.session || !this.config || !this.ort)
|
|
30841
|
+
return null;
|
|
30842
|
+
const textPhonemes = await this.textToPhonemes(text);
|
|
30731
30843
|
const phonemeIds = this.phonemesToIds(textPhonemes);
|
|
30732
30844
|
if (phonemeIds.length === 0)
|
|
30733
|
-
return;
|
|
30845
|
+
return null;
|
|
30734
30846
|
const inputLength = phonemeIds.length;
|
|
30735
30847
|
const inputTensor = new this.ort.Tensor("int64", BigInt64Array.from(phonemeIds.map((id) => BigInt(id))), [1, inputLength]);
|
|
30736
30848
|
const lengthTensor = new this.ort.Tensor("int64", BigInt64Array.from([BigInt(inputLength)]), [1]);
|
|
@@ -30745,23 +30857,28 @@ var init_voice = __esm({
|
|
|
30745
30857
|
}
|
|
30746
30858
|
const result = await this.session.run(feeds);
|
|
30747
30859
|
const audioData = result["output"].data;
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
|
|
30751
|
-
|
|
30752
|
-
|
|
30753
|
-
|
|
30754
|
-
|
|
30755
|
-
|
|
30756
|
-
|
|
30757
|
-
|
|
30758
|
-
const
|
|
30759
|
-
|
|
30760
|
-
|
|
30761
|
-
|
|
30762
|
-
|
|
30763
|
-
|
|
30764
|
-
|
|
30860
|
+
return audioData.length > 0 ? audioData : null;
|
|
30861
|
+
}
|
|
30862
|
+
/**
|
|
30863
|
+
* Pitch-shift audio via linear-interpolation resampling.
|
|
30864
|
+
* Factor > 1.0 = higher pitch (shorter duration).
|
|
30865
|
+
* Factor < 1.0 = lower pitch (longer duration).
|
|
30866
|
+
* Duration is preserved by adjusting the output length accordingly —
|
|
30867
|
+
* we resample to change pitch, then the WAV sample rate stays constant.
|
|
30868
|
+
*/
|
|
30869
|
+
resamplePitch(samples, factor) {
|
|
30870
|
+
const newLen = Math.round(samples.length / factor);
|
|
30871
|
+
if (newLen <= 0)
|
|
30872
|
+
return samples;
|
|
30873
|
+
const out = new Float32Array(newLen);
|
|
30874
|
+
for (let i = 0; i < newLen; i++) {
|
|
30875
|
+
const srcIdx = i * factor;
|
|
30876
|
+
const lo = Math.floor(srcIdx);
|
|
30877
|
+
const hi = Math.min(lo + 1, samples.length - 1);
|
|
30878
|
+
const frac = srcIdx - lo;
|
|
30879
|
+
out[i] = samples[lo] * (1 - frac) + samples[hi] * frac;
|
|
30880
|
+
}
|
|
30881
|
+
return out;
|
|
30765
30882
|
}
|
|
30766
30883
|
// -------------------------------------------------------------------------
|
|
30767
30884
|
// Phonemization
|
|
@@ -35745,8 +35862,12 @@ with summary "no_reply" to silently skip without responding.
|
|
|
35745
35862
|
}
|
|
35746
35863
|
const existing = this.subAgents.get(msg.chatId);
|
|
35747
35864
|
if (existing && !existing.aborted) {
|
|
35748
|
-
existing.runner
|
|
35749
|
-
|
|
35865
|
+
if (existing.runner) {
|
|
35866
|
+
existing.runner.injectUserMessage(msg.text);
|
|
35867
|
+
this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, "mid-conversation steering injected"));
|
|
35868
|
+
} else {
|
|
35869
|
+
this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, "queued (sub-agent still starting)"));
|
|
35870
|
+
}
|
|
35750
35871
|
return;
|
|
35751
35872
|
}
|
|
35752
35873
|
const subAgent = {
|
|
@@ -38168,7 +38289,7 @@ ${entry.fullContent}`
|
|
|
38168
38289
|
const emoCtx = emoState ? { valence: emoState.valence, arousal: emoState.arousal, label: emoState.label, emoji: emoState.emoji } : void 0;
|
|
38169
38290
|
const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {}, vLevel, emoCtx);
|
|
38170
38291
|
renderVoiceText(desc);
|
|
38171
|
-
voice.
|
|
38292
|
+
voice.speakSubordinate(desc, emoCtx);
|
|
38172
38293
|
}
|
|
38173
38294
|
renderToolCallStart(event.toolName ?? "unknown", event.toolArgs ?? {}, config.verbose);
|
|
38174
38295
|
});
|
|
@@ -38206,7 +38327,7 @@ ${entry.fullContent}`
|
|
|
38206
38327
|
const desc = describeToolResult(event.toolName ?? "unknown", event.success ?? false, vLevel, event.content ?? void 0, emoCtx2);
|
|
38207
38328
|
if (desc) {
|
|
38208
38329
|
renderVoiceText(desc);
|
|
38209
|
-
voice.
|
|
38330
|
+
voice.speakSubordinate(desc, emoCtx2);
|
|
38210
38331
|
}
|
|
38211
38332
|
}
|
|
38212
38333
|
});
|
|
@@ -38308,12 +38429,16 @@ ${emotionContext}` : `Working directory: ${repoRoot}`;
|
|
|
38308
38429
|
if (onComplete)
|
|
38309
38430
|
onComplete(result.summary, { turns: result.turns, toolCalls: result.toolCalls, durationMs: result.durationMs, model: config.model });
|
|
38310
38431
|
if (voice?.enabled && result.summary) {
|
|
38311
|
-
|
|
38432
|
+
const emoFinal = emotionEngine?.getState();
|
|
38433
|
+
const emoCtxFinal = emoFinal ? { valence: emoFinal.valence, arousal: emoFinal.arousal, label: emoFinal.label, emoji: emoFinal.emoji } : void 0;
|
|
38434
|
+
voice.speak(describeTaskComplete(result.summary, true, vLevel), emoCtxFinal);
|
|
38312
38435
|
}
|
|
38313
38436
|
} else {
|
|
38314
38437
|
renderTaskIncomplete(result.turns, result.toolCalls, result.durationMs, tokens);
|
|
38315
38438
|
if (voice?.enabled) {
|
|
38316
|
-
|
|
38439
|
+
const emoFinal2 = emotionEngine?.getState();
|
|
38440
|
+
const emoCtxFinal2 = emoFinal2 ? { valence: emoFinal2.valence, arousal: emoFinal2.arousal, label: emoFinal2.label, emoji: emoFinal2.emoji } : void 0;
|
|
38441
|
+
voice.speak(describeTaskComplete("", false, vLevel), emoCtxFinal2);
|
|
38317
38442
|
}
|
|
38318
38443
|
}
|
|
38319
38444
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-agents-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.0",
|
|
4
4
|
"description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -70,6 +70,6 @@
|
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|
|
72
72
|
"moondream": "^0.2.0",
|
|
73
|
-
"open-agents-nexus": "^1.
|
|
73
|
+
"open-agents-nexus": "^1.3.0"
|
|
74
74
|
}
|
|
75
75
|
}
|