open-agents-ai 0.103.54 → 0.103.56

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 +194 -48
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12962,23 +12962,16 @@ async function handleCmd(cmd) {
12962
12962
  },
12963
12963
  });
12964
12964
 
12965
- // Write metering record directly (createFileAuditHook does not capture token data)
12966
- try {
12967
- appendFileSync(meteringFile, JSON.stringify({
12968
- timestamp: Date.now(),
12969
- peerId: request.from || 'unknown',
12970
- service: capName,
12971
- capability: capName,
12972
- model: model.name,
12973
- direction: 'inbound',
12974
- inputBytes: prompt.length,
12975
- outputBytes: (output || '').length,
12976
- tokens: inputTokens + outputTokens,
12977
- inputTokens: inputTokens,
12978
- outputTokens: outputTokens,
12979
- durationMs: Date.now() - logEntry.ts,
12980
- }) + '\\n');
12981
- } catch (mErr) { dlog('metering write error: ' + (mErr.message || mErr)); }
12965
+ // Stash token data for the metering hook (which fires after handler returns
12966
+ // and has the correct peerId from connection context)
12967
+ _tokensByRequest[request.requestId] = {
12968
+ model: model.name,
12969
+ inputBytes: prompt.length,
12970
+ outputBytes: (output || '').length,
12971
+ tokens: inputTokens + outputTokens,
12972
+ inputTokens: inputTokens,
12973
+ outputTokens: outputTokens,
12974
+ };
12982
12975
  } catch (e) {
12983
12976
  dlog('expose: inference error: ' + (e.message || e));
12984
12977
  await swrite({
@@ -13205,9 +13198,37 @@ process.on('unhandledRejection', (reason) => {
13205
13198
  dlog('WARNING: could not patch node.dialProtocol \u2014 node=' + !!_patchNode);
13206
13199
  }
13207
13200
 
13208
- // v1.5.0: Metering is now written directly from inference handlers (with token counts).
13209
- // createFileAuditHook was previously used but produced records with 0 bytes / no tokens.
13210
- // Keep metering hooks for payment tracking only (below).
13201
+ // v1.5.0: Metering hook that enriches nexus records with token counts.
13202
+ // The nexus library fires this hook AFTER handler returns, so it has the correct
13203
+ // peerId from connection context. We stash token data per requestId so the hook
13204
+ // can merge them into one complete record written to metering.jsonl.
13205
+ var _tokensByRequest = {};
13206
+ try {
13207
+ if (nexus.metering && typeof nexus.metering.addHook === 'function') {
13208
+ nexus.metering.addHook(function(record) {
13209
+ // Only enrich inference capability records
13210
+ if (!record.capability || !record.capability.startsWith('inference:')) return;
13211
+ var tokenData = _tokensByRequest[record.id];
13212
+ delete _tokensByRequest[record.id]; // consume
13213
+ try {
13214
+ appendFileSync(meteringFile, JSON.stringify({
13215
+ timestamp: record.timestamp || Date.now(),
13216
+ peerId: record.peerId || 'unknown',
13217
+ service: record.service || record.capability,
13218
+ capability: record.capability,
13219
+ model: tokenData ? tokenData.model : (record.capability || '').replace('inference:', ''),
13220
+ direction: record.direction || 'inbound',
13221
+ inputBytes: tokenData ? tokenData.inputBytes : (record.inputBytes || 0),
13222
+ outputBytes: tokenData ? tokenData.outputBytes : (record.outputBytes || 0),
13223
+ tokens: tokenData ? tokenData.tokens : 0,
13224
+ inputTokens: tokenData ? tokenData.inputTokens : 0,
13225
+ outputTokens: tokenData ? tokenData.outputTokens : 0,
13226
+ durationMs: record.durationMs || 0,
13227
+ }) + '\\n');
13228
+ } catch {}
13229
+ });
13230
+ }
13231
+ } catch {}
13211
13232
 
13212
13233
  // Write payment events to ledger.jsonl
13213
13234
  try {
@@ -28895,7 +28916,8 @@ ${this.formatConnectionInfo()}`);
28895
28916
  user.tokensIn += tokIn;
28896
28917
  user.tokensOut += tokOut;
28897
28918
  if (record.model || record.capability || record.service) {
28898
- const modelName = record.model || (record.capability || record.service || "").replace("inference:", "") || "unknown";
28919
+ let modelName = record.model || (record.capability || record.service || "").replace("inference:", "") || "unknown";
28920
+ modelName = modelName.replace(/^open-agents-/, "").replace(/_latest$/, "").replace(/_/g, ":");
28899
28921
  this._stats.modelUsage.set(modelName, (this._stats.modelUsage.get(modelName) ?? 0) + 1);
28900
28922
  let mm = user.models.get(modelName);
28901
28923
  if (!mm) {
@@ -37145,6 +37167,47 @@ function pick(key, variants) {
37145
37167
  narration.lastVariantIdx[key] = idx;
37146
37168
  return variants[idx];
37147
37169
  }
37170
+ function extractSubAgentTopic(args) {
37171
+ const raw = String(args["description"] ?? args["prompt"] ?? "");
37172
+ if (!raw || raw.length < 5)
37173
+ return "";
37174
+ let text = raw.replace(/[#*`_~\[\]]/g, "").replace(/^\d+[.)]\s*/gm, "").trim();
37175
+ const sentEnd = text.search(/[.!?]\s/);
37176
+ if (sentEnd > 10 && sentEnd < 80) {
37177
+ text = text.slice(0, sentEnd);
37178
+ } else if (text.length > 80) {
37179
+ const cut = text.lastIndexOf(" ", 80);
37180
+ text = text.slice(0, cut > 20 ? cut : 80);
37181
+ }
37182
+ if (text.length > 0)
37183
+ text = text.charAt(0).toLowerCase() + text.slice(1);
37184
+ return text;
37185
+ }
37186
+ function extractShellEssence(cmd) {
37187
+ if (!cmd || cmd.length < 3)
37188
+ return "";
37189
+ const npmTest = cmd.match(/npm\s+test\b/);
37190
+ if (npmTest)
37191
+ return "running tests";
37192
+ const npmBuild = cmd.match(/npm\s+run\s+(\S+)/);
37193
+ if (npmBuild)
37194
+ return `running ${npmBuild[1]}`;
37195
+ const gitCmd = cmd.match(/git\s+(commit|push|pull|merge|rebase|checkout|diff|status|log|stash|add)\b/);
37196
+ if (gitCmd)
37197
+ return `git ${gitCmd[1]}`;
37198
+ if (/npm\s+publish/.test(cmd))
37199
+ return "publishing to npm";
37200
+ const nodeScript = cmd.match(/(?:node|tsx?)\s+(\S+)/);
37201
+ if (nodeScript)
37202
+ return `running ${nodeScript[1].split("/").pop()}`;
37203
+ const curlUrl = cmd.match(/curl\s+.*?(https?:\/\/\S{10,40})/);
37204
+ if (curlUrl)
37205
+ return `fetching ${curlUrl[1].replace(/https?:\/\//, "").split("/")[0]}`;
37206
+ const pnpm = cmd.match(/pnpm\s+(-r\s+)?(\S+)/);
37207
+ if (pnpm)
37208
+ return `pnpm ${pnpm[1] || ""}${pnpm[2]}`.trim();
37209
+ return "";
37210
+ }
37148
37211
  function getShellCategory(cmd) {
37149
37212
  if (/npm\s+test|vitest|jest|mocha/.test(cmd))
37150
37213
  return "test";
@@ -37220,24 +37283,35 @@ function emotionColor(emotion, stark = false, autist = false) {
37220
37283
  return "";
37221
37284
  if (Math.random() > (stark ? 0.5 : 0.3))
37222
37285
  return "";
37286
+ const lastEmoKey = narration.lastVariantIdx["_emo_quadrant"] ?? -1;
37223
37287
  if (emotion.valence > 0.5 && emotion.arousal > 0.6) {
37288
+ narration.lastVariantIdx["_emo_quadrant"] = 1;
37289
+ if (lastEmoKey === 1 && Math.random() < 0.5)
37290
+ return "";
37224
37291
  return pick("emo_excited", stark ? [
37225
37292
  "Now we're cooking. ",
37226
37293
  "This is what I'm talking about. ",
37227
- "Brilliant. Absolutely brilliant. ",
37294
+ "Brilliant. ",
37228
37295
  "Watch this. ",
37229
37296
  "We're onto something here. ",
37230
- "I live for this. ",
37231
- "Oh this is good. "
37297
+ "Oh this is good. ",
37298
+ "The pieces are falling into place. ",
37299
+ "This is where it gets interesting. ",
37300
+ "I can feel this coming together. "
37232
37301
  ] : [
37233
37302
  "Feeling good about this. ",
37234
37303
  "On a roll here. ",
37235
37304
  "This is going great. ",
37236
37305
  "Momentum building. ",
37237
- "Loving this. "
37306
+ "Loving this. ",
37307
+ "Making real progress now. ",
37308
+ "Things are clicking. "
37238
37309
  ]);
37239
37310
  }
37240
37311
  if (emotion.valence < -0.3 && emotion.arousal > 0.5) {
37312
+ narration.lastVariantIdx["_emo_quadrant"] = 2;
37313
+ if (lastEmoKey === 2 && Math.random() < 0.5)
37314
+ return "";
37241
37315
  return pick("emo_stressed", stark ? [
37242
37316
  "Fine. The hard way then. ",
37243
37317
  "This is getting personal. ",
@@ -37245,41 +37319,54 @@ function emotionColor(emotion, stark = false, autist = false) {
37245
37319
  "Come on. Think. ",
37246
37320
  "Not today. Not this bug. ",
37247
37321
  "I've beaten worse. ",
37248
- "Pain is just information. "
37322
+ "There's got to be a way. ",
37323
+ "Okay, new angle. "
37249
37324
  ] : [
37250
37325
  "Pushing through. ",
37251
37326
  "Staying focused. ",
37252
37327
  "Not giving up. ",
37253
37328
  "Determined to crack this. ",
37254
- "Bear with me. "
37329
+ "Bear with me. ",
37330
+ "Working through this. ",
37331
+ "Let me rethink this. "
37255
37332
  ]);
37256
37333
  }
37257
37334
  if (emotion.valence > 0.3 && emotion.arousal < 0.3) {
37335
+ narration.lastVariantIdx["_emo_quadrant"] = 3;
37336
+ if (lastEmoKey === 3 && Math.random() < 0.5)
37337
+ return "";
37258
37338
  return pick("emo_calm", stark ? [
37259
37339
  "Clean. Just how I like it. ",
37260
37340
  "Smooth. ",
37261
37341
  "Textbook. ",
37262
37342
  "That's the stuff. ",
37263
- "Effortless. "
37343
+ "Effortless. ",
37344
+ "Exactly as expected. "
37264
37345
  ] : [
37265
37346
  "Nice and steady. ",
37266
37347
  "Smooth sailing. ",
37267
37348
  "Easy does it. ",
37268
- "Taking it easy. "
37349
+ "Taking it easy. ",
37350
+ "Going well. "
37269
37351
  ]);
37270
37352
  }
37271
37353
  if (emotion.valence < -0.2 && emotion.arousal < 0.3) {
37354
+ narration.lastVariantIdx["_emo_quadrant"] = 4;
37355
+ if (lastEmoKey === 4 && Math.random() < 0.5)
37356
+ return "";
37272
37357
  return pick("emo_subdued", stark ? [
37273
37358
  "Careful now. ",
37274
37359
  "One wrong move and this unravels. ",
37275
37360
  "Measured steps. ",
37276
37361
  "Trust the process. ",
37277
- "Precision matters here. "
37362
+ "Precision matters here. ",
37363
+ "Methodical. "
37278
37364
  ] : [
37279
37365
  "Being careful here. ",
37280
37366
  "Treading carefully. ",
37281
37367
  "Let me be precise. ",
37282
- "Taking extra care. "
37368
+ "Taking extra care. ",
37369
+ "One step at a time. "
37283
37370
  ]);
37284
37371
  }
37285
37372
  return "";
@@ -37312,9 +37399,16 @@ function describeToolCall(toolName, args, personality = 2, emotion, stark = fals
37312
37399
  base = pick(poolKey, (FILE_EDIT_VARIANTS[tier] ?? FILE_EDIT_VARIANTS.terse)(file, args));
37313
37400
  break;
37314
37401
  case "shell": {
37315
- const cat = getShellCategory(String(args["command"] ?? ""));
37316
- const pool = SHELL_VARIANTS[cat] ?? SHELL_VARIANTS.generic;
37317
- base = pick(`shell_${cat}_${tier}`, pool[tier] ?? pool.terse);
37402
+ const cmd = String(args["command"] ?? "");
37403
+ const cat = getShellCategory(cmd);
37404
+ const essence = extractShellEssence(cmd);
37405
+ if (essence && tier !== "terse") {
37406
+ const essenceVariants = tier === "chatty" ? [`Alright, ${essence}`, `Let me handle ${essence}`, `Time to ${essence.startsWith("running") ? essence : "run " + essence}`] : [`Now ${essence}`, essence.charAt(0).toUpperCase() + essence.slice(1)];
37407
+ base = pick(`shell_ess_${tier}`, essenceVariants);
37408
+ } else {
37409
+ const pool = SHELL_VARIANTS[cat] ?? SHELL_VARIANTS.generic;
37410
+ base = pick(`shell_${cat}_${tier}`, pool[tier] ?? pool.terse);
37411
+ }
37318
37412
  break;
37319
37413
  }
37320
37414
  case "grep_search":
@@ -37514,9 +37608,47 @@ function extractResultDigest(toolName, content) {
37514
37608
  const exitMatch = text.match(/exit\s*(?:code)?[:\s]*(\d+)/i);
37515
37609
  if (exitMatch && exitMatch[1] !== "0")
37516
37610
  nuggets.push(`exit code ${exitMatch[1]}`);
37611
+ if (nuggets.length === 0) {
37612
+ const contentDigest = extractContentSummary(text, toolName);
37613
+ if (contentDigest)
37614
+ nuggets.push(contentDigest);
37615
+ }
37517
37616
  const digest = nuggets.slice(0, 3).join(", ");
37518
37617
  return digest.length > 100 ? digest.slice(0, 97) + "..." : digest;
37519
37618
  }
37619
+ function extractContentSummary(text, toolName) {
37620
+ let cleaned = text.replace(/^\s*\d+[│|:→]\s*/gm, "").replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z?\s*/g, "").replace(/^[\s*#=-]+$/gm, "").replace(/```[\s\S]*?```/g, "").replace(/^\s*(import|export|const|let|var|function|class|interface|type)\s/gm, "").replace(/[{}()\[\];]/g, "").trim();
37621
+ const lines = cleaned.split("\n").map((l) => l.trim()).filter((l) => l.length > 10);
37622
+ for (const line of lines) {
37623
+ const wordChars = line.replace(/[^a-zA-Z\s]/g, "").trim();
37624
+ if (wordChars.length < line.length * 0.4)
37625
+ continue;
37626
+ if (/^(\/|\.\/)/.test(line))
37627
+ continue;
37628
+ if (/^\s*[{<]/.test(line))
37629
+ continue;
37630
+ let summary = line;
37631
+ if (summary.length > 80) {
37632
+ const cut = summary.lastIndexOf(" ", 80);
37633
+ summary = summary.slice(0, cut > 20 ? cut : 80);
37634
+ }
37635
+ if (summary.length > 0) {
37636
+ summary = summary.charAt(0).toLowerCase() + summary.slice(1);
37637
+ summary = summary.replace(/[.,;:!?]+$/, "");
37638
+ }
37639
+ return summary;
37640
+ }
37641
+ if (toolName === "sub_agent") {
37642
+ const summaryMatch = text.match(/(?:summary|created|completed|result)[:\s]+(.{10,80}?)(?:\n|$)/i);
37643
+ if (summaryMatch) {
37644
+ let s = summaryMatch[1].trim();
37645
+ if (s.length > 0)
37646
+ s = s.charAt(0).toLowerCase() + s.slice(1);
37647
+ return s;
37648
+ }
37649
+ }
37650
+ return "";
37651
+ }
37520
37652
  function describeToolResult(toolName, success, personality = 2, resultContent, emotion, stark = false) {
37521
37653
  if (toolName === "task_complete")
37522
37654
  return "";
@@ -37532,6 +37664,16 @@ function describeToolResult(toolName, success, personality = 2, resultContent, e
37532
37664
  return "";
37533
37665
  const base = pick(`result_ok_${tier}`, RESULT_SUCCESS_VARIANTS[tier] ?? RESULT_SUCCESS_VARIANTS.terse);
37534
37666
  if (digest && personality >= 2) {
37667
+ if (tier === "chatty" && digest.length > 15) {
37668
+ if (Math.random() < 0.5) {
37669
+ const contentLeads = [
37670
+ `${emo}Interesting, ${digest}`,
37671
+ `${emo}So we've got ${digest}`,
37672
+ `${emo}${digest.charAt(0).toUpperCase() + digest.slice(1)}, that's useful`
37673
+ ];
37674
+ return pick("digest_lead", contentLeads);
37675
+ }
37676
+ }
37535
37677
  const connectors = tier === "chatty" ? [` \u2014 ${digest}`, `, I see ${digest}`, `. Looks like ${digest}`, `, showing ${digest}`] : tier === "conv" ? [` \u2014 ${digest}`, `, ${digest}`, `. Shows ${digest}`] : [`: ${digest}`];
37536
37678
  return emo + base + pick("digest_conn_ok", connectors);
37537
37679
  }
@@ -38657,18 +38799,18 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
38657
38799
  ]
38658
38800
  };
38659
38801
  SUB_AGENT_VARIANTS = {
38660
- terse: () => [`Delegating to sub agent`, `Spawning sub agent`, `Handing off`],
38661
- conv: () => [
38662
- `Handing this off to a sub agent`,
38663
- `Bringing in some help for this`,
38664
- `Delegating this part of the work`
38665
- ],
38666
- chatty: () => [
38667
- `Bringing in reinforcements for this one`,
38668
- `Let me hand this off to a specialist`,
38669
- `Calling in backup on this`,
38670
- `This needs a dedicated agent, passing it along`
38671
- ]
38802
+ terse: (_f, a) => {
38803
+ const topic = extractSubAgentTopic(a);
38804
+ return topic ? [`Sub agent: ${topic}`, `Delegating: ${topic}`] : [`Delegating to sub agent`, `Spawning sub agent`];
38805
+ },
38806
+ conv: (_f, a) => {
38807
+ const topic = extractSubAgentTopic(a);
38808
+ return topic ? [`Handing off to a sub agent to ${topic}`, `Bringing in help to ${topic}`, `Delegating the ${topic} work`] : [`Handing this off to a sub agent`, `Bringing in some help for this`, `Delegating this part of the work`];
38809
+ },
38810
+ chatty: (_f, a) => {
38811
+ const topic = extractSubAgentTopic(a);
38812
+ return topic ? [`Spinning up a sub agent to ${topic}`, `This part needs a specialist, delegating to ${topic}`, `Calling in backup to handle ${topic}`] : [`Bringing in reinforcements for this one`, `Let me hand this off to a specialist`, `Calling in backup on this`];
38813
+ }
38672
38814
  };
38673
38815
  SHELL_VARIANTS = {
38674
38816
  test: {
@@ -48255,8 +48397,12 @@ ${result.text}`;
48255
48397
  if (parsed.summary)
48256
48398
  steering = parsed.summary;
48257
48399
  } catch {
48258
- if (result.summary)
48259
- steering = `USER STEERING: ${result.summary}`;
48400
+ const raw = result.summary || "";
48401
+ const ackMatch = raw.match(/acknowledgment[:\s]*["']?([^"'\n]{5,80})["']?/i) || raw.match(/^([^.!?\n]{5,80}[.!?])/);
48402
+ if (ackMatch)
48403
+ acknowledgment = ackMatch[1].trim();
48404
+ if (raw.length > 10)
48405
+ steering = `USER STEERING: ${raw}`;
48260
48406
  }
48261
48407
  if (voiceEngine?.enabled) {
48262
48408
  writeContent(() => renderVoiceText(acknowledgment));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.54",
3
+ "version": "0.103.56",
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",