open-agents-ai 0.61.0 → 0.62.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.
- package/README.md +29 -0
- package/dist/index.js +89 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ An autonomous multi-turn tool-calling agent that reads your code, makes changes,
|
|
|
40
40
|
- **Autoresearch Swarm** — 5-agent GPU experiment loop during REM sleep: Researcher, Monitor, Evaluator, Critic, Flow Maintainer autonomously run ML training experiments, keep improvements, discard regressions
|
|
41
41
|
- **Live Listen** — bidirectional voice communication with real-time Whisper transcription
|
|
42
42
|
- **Live Voice Session** — `/listen` with `/voice` enabled spawns a cloudflared tunnel with a real-time WebSocket audio endpoint. A floating presence UI shows live transcription, connected users, and audio visualization. Echo cancellation prevents TTS feedback loops
|
|
43
|
+
- **Call Sub-Agent** — each WebSocket caller gets a dedicated AgenticRunner for low-latency voice-to-voice loops, with admin/public access tiers and bidirectional activity sharing with the main agent
|
|
43
44
|
- **Telegram Voice** — `/voice` enabled via Telegram forwards TTS audio as voice messages alongside text responses. Incoming voice messages are auto-transcribed and handled as text
|
|
44
45
|
- **Neural TTS** — hear what the agent is doing via GLaDOS or Overwatch ONNX voices, with personality-driven expressiveness
|
|
45
46
|
- **Personality Core** — SAC framework-based style control (concise/balanced/verbose/pedagogical) that shapes agent response depth, voice expressiveness, and system prompt behavior
|
|
@@ -1051,6 +1052,34 @@ When `/voice` is enabled and the Telegram bridge is active:
|
|
|
1051
1052
|
|
|
1052
1053
|
Cloudflared is automatically installed at startup alongside other dependencies (moondream, tesseract, transcribe-cli). The install is non-blocking and runs in the background.
|
|
1053
1054
|
|
|
1055
|
+
### Call Sub-Agent Architecture
|
|
1056
|
+
|
|
1057
|
+
Each WebSocket caller in a live voice session gets a **dedicated AgenticRunner** — a fully independent agent instance that handles the voice-to-text-to-LLM-to-TTS-to-reply pipeline with minimal latency.
|
|
1058
|
+
|
|
1059
|
+
**Access tiers** — callers connect at one of two privilege levels:
|
|
1060
|
+
|
|
1061
|
+
| Tier | URL | Tool Access | Max Turns |
|
|
1062
|
+
|------|-----|-------------|-----------|
|
|
1063
|
+
| **Admin** | `wss://…?key=<session-key>` | Full tool set (12 tools: file read/write/edit, shell, grep, glob, list directory, web search/fetch, memory read/write/search) | 15 |
|
|
1064
|
+
| **Public** | `wss://…` (no key) | Read-only tools (6 tools: file read, grep, glob, list directory, memory read/search) | 5 |
|
|
1065
|
+
|
|
1066
|
+
The **session key** is a `crypto.randomBytes(16)` hex string generated per TUI session and displayed in the terminal when the voice session starts. Passing it as the `?key=` URL parameter on the WebSocket connection upgrades the caller to admin access.
|
|
1067
|
+
|
|
1068
|
+
**ActivityFeed** — the main TUI agent and all call sub-agents share a bidirectional ring buffer (max 100 entries). Tool calls and results from call sub-agents surface in the main terminal waterfall, and the main agent's activity is visible to connected callers. Each entry carries timestamp, source (main/call), sourceId, tool name, success status, and a summary. Admin callers see verbose timestamped activity; public callers see surface-level summaries.
|
|
1069
|
+
|
|
1070
|
+
**Per-client lifecycle** — on WebSocket connect, a `CallSubAgent` is instantiated with its own `AgenticRunner`, `OllamaAgenticBackend`, and conversation history. Transcripts are queued FIFO if the agent is mid-response, ensuring nothing is dropped. On disconnect, the sub-agent is disposed and removed from the active client map.
|
|
1071
|
+
|
|
1072
|
+
### Content-Aware Voice Narration
|
|
1073
|
+
|
|
1074
|
+
The stochastic narration engine generates spoken descriptions of what the agent is doing for TTS output. Instead of preset phrases, it uses:
|
|
1075
|
+
|
|
1076
|
+
- **Variant pools** — 6-10 phrasings per tool per personality tier (terse/conversational/chatty), selected randomly with no back-to-back repeats
|
|
1077
|
+
- **Context modifiers** — tracks session state (consecutive errors, file revisits, progress beats) to add natural transitions like "Third time's the charm" or "Coming back to"
|
|
1078
|
+
- **Content digests** — extracts key details from actual tool result content (ETH balances, test results, error messages, wallet addresses, status tags, version numbers) and weaves them into the spoken narration. Instead of "Got it", the agent says "Got it — 2.5 ETH, address 0x9fe7F838..." or "That worked, 42 tests passed"
|
|
1079
|
+
- **Cross-tool context** — the digest from a tool result optionally carries forward into the next tool call description, so the agent can say "Checking that file, following up on 2.5 ETH" instead of repeating a generic opener
|
|
1080
|
+
- **Personality scaling** — terse mode (level 1-2) uses short functional descriptions; conversational (3) adds natural phrasing; chatty (4-5) adds theatrical commentary and content references
|
|
1081
|
+
- **Natural silence** — on bland successes without notable content, ~40% of the time the narration is skipped entirely for a more natural rhythm
|
|
1082
|
+
|
|
1054
1083
|
## Personality Core — SAC Framework Style Control
|
|
1055
1084
|
|
|
1056
1085
|
The personality system controls how the agent communicates — from silent operator to teacher mode. It's based on the **SAC framework** (arXiv:2506.20993) which models personality along five behavioral intensity dimensions rather than binary trait toggles.
|
package/dist/index.js
CHANGED
|
@@ -26999,6 +26999,7 @@ function resetNarrationContext() {
|
|
|
26999
26999
|
narration.lastFile = "";
|
|
27000
27000
|
narration.filesSeen.clear();
|
|
27001
27001
|
narration.lastVariantIdx = {};
|
|
27002
|
+
narration.lastResultDigest = "";
|
|
27002
27003
|
}
|
|
27003
27004
|
function pick(key, variants) {
|
|
27004
27005
|
if (variants.length === 1)
|
|
@@ -27234,31 +27235,102 @@ function describeToolCall(toolName, args, personality = 2) {
|
|
|
27234
27235
|
break;
|
|
27235
27236
|
}
|
|
27236
27237
|
}
|
|
27237
|
-
|
|
27238
|
-
|
|
27239
|
-
|
|
27240
|
-
|
|
27241
|
-
|
|
27242
|
-
|
|
27238
|
+
let result;
|
|
27239
|
+
if (!prefix) {
|
|
27240
|
+
result = base;
|
|
27241
|
+
} else if (prefix.endsWith(". ") || prefix.endsWith("! ")) {
|
|
27242
|
+
result = prefix + base;
|
|
27243
|
+
} else if (base.length > 0) {
|
|
27244
|
+
result = prefix + base.charAt(0).toLowerCase() + base.slice(1);
|
|
27245
|
+
} else {
|
|
27246
|
+
result = base;
|
|
27243
27247
|
}
|
|
27244
|
-
|
|
27248
|
+
if (narration.lastResultDigest && personality >= 3 && Math.random() < 0.4) {
|
|
27249
|
+
const ctx = narration.lastResultDigest;
|
|
27250
|
+
const bridges = tier === "chatty" ? [` \u2014 earlier I saw ${ctx}`, `, building on what we found: ${ctx}`, `. Given that we got ${ctx}`] : [` \u2014 saw ${ctx} earlier`, `, following up on ${ctx}`];
|
|
27251
|
+
result += pick("ctx_lastresult", bridges);
|
|
27252
|
+
narration.lastResultDigest = "";
|
|
27253
|
+
}
|
|
27254
|
+
return result;
|
|
27245
27255
|
}
|
|
27246
|
-
function
|
|
27256
|
+
function extractResultDigest(toolName, content) {
|
|
27257
|
+
if (!content || content.length < 5)
|
|
27258
|
+
return "";
|
|
27259
|
+
const text = content.slice(0, 2e3);
|
|
27260
|
+
const nuggets = [];
|
|
27261
|
+
const ethMatch = text.match(/([\d.]+)\s*ETH/i);
|
|
27262
|
+
if (ethMatch)
|
|
27263
|
+
nuggets.push(`${ethMatch[1]} ETH`);
|
|
27264
|
+
const tokenMatch = text.match(/([\d,.]+)\s*(USDC|USDT|DAI|BTC|SOL|MATIC|tokens?)\b/i);
|
|
27265
|
+
if (tokenMatch && !nuggets.length)
|
|
27266
|
+
nuggets.push(`${tokenMatch[1]} ${tokenMatch[2]}`);
|
|
27267
|
+
const addrMatch = text.match(/(0x[0-9a-fA-F]{8})[0-9a-fA-F]+/);
|
|
27268
|
+
if (addrMatch)
|
|
27269
|
+
nuggets.push(`address ${addrMatch[1]}...`);
|
|
27270
|
+
const httpMatch = text.match(/(?:status|code)[:\s]*(\d{3})\b/i);
|
|
27271
|
+
if (httpMatch)
|
|
27272
|
+
nuggets.push(`status ${httpMatch[1]}`);
|
|
27273
|
+
const testPass = text.match(/(\d+)\s*(?:tests?\s*)?pass(?:ed|ing)?/i);
|
|
27274
|
+
const testFail = text.match(/(\d+)\s*(?:tests?\s*)?fail(?:ed|ing|ures?)?/i);
|
|
27275
|
+
if (testPass || testFail) {
|
|
27276
|
+
const parts = [];
|
|
27277
|
+
if (testPass)
|
|
27278
|
+
parts.push(`${testPass[1]} passed`);
|
|
27279
|
+
if (testFail)
|
|
27280
|
+
parts.push(`${testFail[1]} failed`);
|
|
27281
|
+
nuggets.push(parts.join(", "));
|
|
27282
|
+
}
|
|
27283
|
+
const errMatch = text.match(/(?:error|Error|ERROR)[:\s]+(.{5,50}?)(?:\n|$)/);
|
|
27284
|
+
if (errMatch)
|
|
27285
|
+
nuggets.push(`error: ${errMatch[1].trim()}`);
|
|
27286
|
+
const statusMatch = text.match(/\[(running|complete|completed|failed|pending|success|stopped|error)\]/i);
|
|
27287
|
+
if (statusMatch)
|
|
27288
|
+
nuggets.push(statusMatch[1].toLowerCase());
|
|
27289
|
+
const versionMatch = text.match(/\bv?(\d+\.\d+\.\d+)\b/);
|
|
27290
|
+
if (versionMatch && !nuggets.some((n) => n.includes(versionMatch[1]))) {
|
|
27291
|
+
nuggets.push(`version ${versionMatch[1]}`);
|
|
27292
|
+
}
|
|
27293
|
+
const matchCount = text.match(/(\d+)\s*(?:matches|results|occurrences)/i);
|
|
27294
|
+
if (matchCount)
|
|
27295
|
+
nuggets.push(`${matchCount[1]} matches`);
|
|
27296
|
+
const sizeMatch = text.match(/([\d.]+)\s*(GB|MB|KB|bytes)\b/i);
|
|
27297
|
+
if (sizeMatch)
|
|
27298
|
+
nuggets.push(`${sizeMatch[1]} ${sizeMatch[2]}`);
|
|
27299
|
+
const gasMatch = text.match(/gas[:\s]*([\d,]+)/i);
|
|
27300
|
+
if (gasMatch)
|
|
27301
|
+
nuggets.push(`gas ${gasMatch[1]}`);
|
|
27302
|
+
const exitMatch = text.match(/exit\s*(?:code)?[:\s]*(\d+)/i);
|
|
27303
|
+
if (exitMatch && exitMatch[1] !== "0")
|
|
27304
|
+
nuggets.push(`exit code ${exitMatch[1]}`);
|
|
27305
|
+
const digest = nuggets.slice(0, 3).join(", ");
|
|
27306
|
+
return digest.length > 100 ? digest.slice(0, 97) + "..." : digest;
|
|
27307
|
+
}
|
|
27308
|
+
function describeToolResult(toolName, success, personality = 2, resultContent) {
|
|
27247
27309
|
if (toolName === "task_complete")
|
|
27248
27310
|
return "";
|
|
27249
27311
|
const tier = getTier(personality);
|
|
27312
|
+
const digest = resultContent ? extractResultDigest(toolName, resultContent) : "";
|
|
27313
|
+
if (digest) {
|
|
27314
|
+
narration.lastResultDigest = digest;
|
|
27315
|
+
}
|
|
27250
27316
|
if (success) {
|
|
27251
27317
|
narration.consecutiveErrors = 0;
|
|
27252
|
-
if (personality >= 3 && Math.random() < 0.4)
|
|
27318
|
+
if (!digest && personality >= 3 && Math.random() < 0.4)
|
|
27253
27319
|
return "";
|
|
27254
|
-
|
|
27320
|
+
const base = pick(`result_ok_${tier}`, RESULT_SUCCESS_VARIANTS[tier] ?? RESULT_SUCCESS_VARIANTS.terse);
|
|
27321
|
+
if (digest && personality >= 2) {
|
|
27322
|
+
const connectors = tier === "chatty" ? [` \u2014 ${digest}`, `, I see ${digest}`, `. Looks like ${digest}`, `, showing ${digest}`] : tier === "conv" ? [` \u2014 ${digest}`, `, ${digest}`, `. Shows ${digest}`] : [`: ${digest}`];
|
|
27323
|
+
return base + pick("digest_conn_ok", connectors);
|
|
27324
|
+
}
|
|
27325
|
+
return base;
|
|
27255
27326
|
}
|
|
27256
27327
|
narration.consecutiveErrors++;
|
|
27257
27328
|
narration.totalErrors++;
|
|
27258
|
-
|
|
27259
|
-
|
|
27329
|
+
const failBase = narration.consecutiveErrors >= 3 ? pick(`result_multifail_${tier}`, RESULT_MULTI_FAIL_VARIANTS[tier] ?? RESULT_MULTI_FAIL_VARIANTS.terse) : pick(`result_fail_${tier}`, RESULT_FAIL_VARIANTS[tier] ?? RESULT_FAIL_VARIANTS.terse);
|
|
27330
|
+
if (digest && personality >= 2) {
|
|
27331
|
+
return `${failBase} \u2014 ${digest}`;
|
|
27260
27332
|
}
|
|
27261
|
-
return
|
|
27333
|
+
return failBase;
|
|
27262
27334
|
}
|
|
27263
27335
|
function describeTaskComplete(summary, completed, personality = 2) {
|
|
27264
27336
|
const truncated = summary.length > 300 ? summary.slice(0, 300) + "..." : summary;
|
|
@@ -27848,7 +27920,8 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
27848
27920
|
lastTool: "",
|
|
27849
27921
|
lastFile: "",
|
|
27850
27922
|
filesSeen: /* @__PURE__ */ new Set(),
|
|
27851
|
-
lastVariantIdx: {}
|
|
27923
|
+
lastVariantIdx: {},
|
|
27924
|
+
lastResultDigest: ""
|
|
27852
27925
|
};
|
|
27853
27926
|
FILE_READ_VARIANTS = {
|
|
27854
27927
|
terse: (f) => [`Reading ${f}`, `Opening ${f}`, `Loading ${f}`],
|
|
@@ -34542,8 +34615,8 @@ ${entry.fullContent}`
|
|
|
34542
34615
|
const sizeStr = resultLen > 0 ? ` | ${resultLen.toLocaleString()} chars (~${Math.ceil(resultLen / 4).toLocaleString()} tokens)` : "";
|
|
34543
34616
|
renderVerbose(`${event.toolName ?? "unknown"}: ${durStr}${sizeStr}`);
|
|
34544
34617
|
}
|
|
34545
|
-
if (voice?.enabled
|
|
34546
|
-
const desc = describeToolResult(event.toolName ?? "unknown", false, vLevel);
|
|
34618
|
+
if (voice?.enabled) {
|
|
34619
|
+
const desc = describeToolResult(event.toolName ?? "unknown", event.success ?? false, vLevel, event.content ?? void 0);
|
|
34547
34620
|
if (desc) {
|
|
34548
34621
|
renderVoiceText(desc);
|
|
34549
34622
|
voice.speak(desc);
|
package/package.json
CHANGED