open-agents-ai 0.102.0 → 0.103.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 CHANGED
@@ -5,11 +5,6 @@
5
5
  No API keys. No cloud. Your code never leaves your machine.
6
6
  </p>
7
7
 
8
- <p align="center">
9
- <code>⠀⠀⠀⠁⠉⠛⠿⣿⣿⣿⣿⡿⠿⠛⠉⠁⠀⠀⠀⠀⠀⠀⠁⠉⠛⠿⣿⣿⣿⣿⡿⠿⠛⠉⠁⠀⠀⠀⠀⠀⠀⠁⠉⠛⠿⣿⣿⣿⣿⡿⠿⠛⠉⠁⠀⠀</code><br>
10
- <sub>Neural braille visualizer — SNR-modulated wave entropy with brain-region color themes</sub>
11
- </p>
12
-
13
8
  <p align="center">
14
9
  <a href="https://www.npmjs.com/package/open-agents-ai"><img src="https://img.shields.io/npm/v/open-agents-ai?color=7C3AED&style=flat-square" alt="npm version" /></a>
15
10
  <a href="https://www.npmjs.com/package/open-agents-ai"><img src="https://img.shields.io/npm/dm/open-agents-ai?color=06B6D4&style=flat-square" alt="npm downloads" /></a>
@@ -95,7 +90,7 @@ D8AgCTrxpDKD5meJ2bpAfVwcST3NF3EPuy9xczYycnXn
95
90
  - **Secret vault** — `/secrets` manages API keys and credentials with AES-256-GCM encrypted persistence; secrets are automatically redacted before sending to untrusted inference peers and re-injected on response
96
91
  - **Auto-expanding context** — detects RAM/VRAM and creates an optimized model variant on first run
97
92
  - **Mid-task steering** — type while the agent works to add context without interrupting
98
- - **Smart compaction** — 6 context compaction strategies (default, aggressive, decisions, errors, summary, structured) with research-backed design
93
+ - **Smart compaction** — 6 context compaction strategies (default, aggressive, decisions, errors, summary, structured) with ARC-inspired active context revision ([arXiv:2601.12030](https://arxiv.org/abs/2601.12030)) that preserves structural file content through compaction, preventing small-model repetitive loops at the root cause
99
94
  - **Memex experience archive** — large tool outputs archived during compaction with hash-based retrieval
100
95
  - **Persistent memory** — learned patterns stored in `.oa/memory/` across sessions
101
96
  - **Session context persistence** — auto-saves context on task completion, manual `/context save|restore` across sessions
package/dist/index.js CHANGED
@@ -16861,7 +16861,13 @@ var init_ralphLoop = __esm({
16861
16861
  });
16862
16862
 
16863
16863
  // packages/orchestrator/dist/personality.js
16864
- function compilePersonalityPrompt(profile) {
16864
+ function compilePersonalityPrompt(profile, presetName) {
16865
+ if (presetName === "stark") {
16866
+ return loadPrompt("personality/level-stark.md");
16867
+ }
16868
+ if (presetName === "autist") {
16869
+ return loadPrompt("personality/level-autist.md");
16870
+ }
16865
16871
  const avg = (profile.frequency + profile.depth + profile.threshold + profile.effort + profile.willingness) / 5;
16866
16872
  if (avg <= 1.5) {
16867
16873
  return loadPrompt("personality/level-1-minimal.md");
@@ -16893,7 +16899,11 @@ var init_personality = __esm({
16893
16899
  /** Thorough explainer — narrates reasoning */
16894
16900
  verbose: { frequency: 5, depth: 4, threshold: 4, effort: 4, willingness: 4 },
16895
16901
  /** Teacher mode — maximum explanation */
16896
- pedagogical: { frequency: 5, depth: 5, threshold: 5, effort: 5, willingness: 5 }
16902
+ pedagogical: { frequency: 5, depth: 5, threshold: 5, effort: 5, willingness: 5 },
16903
+ /** Stark — direct, intense, sardonic edge. High narration with dramatic emotional swings. */
16904
+ stark: { frequency: 4, depth: 4, threshold: 5, effort: 3, willingness: 5 },
16905
+ /** Autist — savant precision. Exhaustive planning, minimal execution, zero waste. */
16906
+ autist: { frequency: 1, depth: 5, threshold: 1, effort: 5, willingness: 1 }
16897
16907
  };
16898
16908
  PRESET_NAMES = Object.keys(PERSONALITY_PRESETS);
16899
16909
  }
@@ -17028,7 +17038,8 @@ var init_agenticRunner = __esm({
17028
17038
  bruteForceMaxCycles: options?.bruteForceMaxCycles ?? 100,
17029
17039
  modelTier: options?.modelTier ?? "large",
17030
17040
  contextWindowSize: options?.contextWindowSize ?? 0,
17031
- personality: options?.personality ?? PERSONALITY_PRESETS.balanced
17041
+ personality: options?.personality ?? PERSONALITY_PRESETS.balanced,
17042
+ personalityName: options?.personalityName ?? ""
17032
17043
  };
17033
17044
  }
17034
17045
  /** Update context window size (e.g. after querying Ollama /api/show) */
@@ -17083,7 +17094,7 @@ var init_agenticRunner = __esm({
17083
17094
  content: basePrompt,
17084
17095
  tokenEstimate: Math.ceil(basePrompt.length / 4)
17085
17096
  });
17086
- const personalitySuffix = this.options.personality ? compilePersonalityPrompt(this.options.personality) : "";
17097
+ const personalitySuffix = this.options.personality ? compilePersonalityPrompt(this.options.personality, this.options.personalityName) : "";
17087
17098
  if (personalitySuffix) {
17088
17099
  sections.push({
17089
17100
  label: "c_state",
@@ -17353,6 +17364,8 @@ TASK: ${task}` : task }
17353
17364
  let consecutiveTextOnly = 0;
17354
17365
  const MAX_CONSECUTIVE_TEXT_ONLY = 3;
17355
17366
  let narratedToolCallCount = 0;
17367
+ let consecutiveEmptyResponses = 0;
17368
+ const recentToolResults = /* @__PURE__ */ new Map();
17356
17369
  for (let turn = 0; turn < this.options.maxTurns; turn++) {
17357
17370
  if (this._paused) {
17358
17371
  const shouldContinue = await this.waitIfPaused();
@@ -17480,6 +17493,30 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
17480
17493
  if (!choice)
17481
17494
  break;
17482
17495
  const msg = choice.message;
17496
+ const isEmptyResponse = (!msg.content || !msg.content.trim()) && (!msg.toolCalls || msg.toolCalls.length === 0);
17497
+ if (isEmptyResponse) {
17498
+ consecutiveEmptyResponses++;
17499
+ if (consecutiveEmptyResponses >= 2) {
17500
+ this.emit({
17501
+ type: "status",
17502
+ content: `${consecutiveEmptyResponses} consecutive empty responses \u2014 injecting recovery nudge`,
17503
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17504
+ });
17505
+ messages.push({
17506
+ role: "user",
17507
+ content: `[SYSTEM] You produced ${consecutiveEmptyResponses} empty responses in a row. You MUST take action now. Your task: ${this._taskState.goal}
17508
+
17509
+ ` + (this._taskState.nextAction ? `**DO THIS NOW:** ${this._taskState.nextAction}
17510
+
17511
+ ` : "") + `Call a tool to make progress. If you don't know what to do, call list_directory(".") to orient yourself.`
17512
+ });
17513
+ if (consecutiveEmptyResponses >= 4) {
17514
+ this._pendingCompaction = "aggressive";
17515
+ }
17516
+ }
17517
+ continue;
17518
+ }
17519
+ consecutiveEmptyResponses = 0;
17483
17520
  if (msg.toolCalls && msg.toolCalls.length > 0) {
17484
17521
  consecutiveTextOnly = 0;
17485
17522
  messages.push({
@@ -17513,6 +17550,48 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
17513
17550
  toolCallCount++;
17514
17551
  const argsKey = Object.entries(tc.arguments ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${typeof v === "string" ? v.slice(0, 80) : JSON.stringify(v)}`).join(",");
17515
17552
  toolCallLog.push({ name: tc.name, argsKey });
17553
+ const toolFingerprint = `${tc.name}:${argsKey}`;
17554
+ const isReadLike = ![
17555
+ "file_write",
17556
+ "file_edit",
17557
+ "shell",
17558
+ "task_complete",
17559
+ "batch_edit",
17560
+ "file_patch",
17561
+ "memory_write",
17562
+ "aiwg_setup",
17563
+ "aiwg_workflow",
17564
+ "create_tool",
17565
+ "skill_build",
17566
+ "skill_execute",
17567
+ "sub_agent",
17568
+ "priority_delegate"
17569
+ ].includes(tc.name);
17570
+ const cachedResult = recentToolResults.get(toolFingerprint);
17571
+ if (isReadLike && cachedResult !== void 0) {
17572
+ this.emit({
17573
+ type: "tool_call",
17574
+ toolName: tc.name,
17575
+ toolArgs: tc.arguments,
17576
+ turn,
17577
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17578
+ });
17579
+ const dedupWarning = `[DUPLICATE CALL \u2014 you already called ${tc.name} with these exact arguments. The result is identical. Do NOT call this again. Use the data you already have to make progress.]
17580
+
17581
+ `;
17582
+ const truncatedCache = cachedResult.length > 500 ? cachedResult.slice(0, 500) + `
17583
+ ... [${cachedResult.length - 500} chars omitted \u2014 same as before]` : cachedResult;
17584
+ const dedupOutput = dedupWarning + truncatedCache;
17585
+ this.emit({
17586
+ type: "tool_result",
17587
+ toolName: tc.name,
17588
+ success: true,
17589
+ content: dedupWarning.slice(0, 100),
17590
+ turn,
17591
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17592
+ });
17593
+ return { tc, output: dedupOutput };
17594
+ }
17516
17595
  this.emit({
17517
17596
  type: "tool_call",
17518
17597
  toolName: tc.name,
@@ -17538,6 +17617,14 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
17538
17617
  result = { success: false, output: "", error: err instanceof Error ? err.message : String(err) };
17539
17618
  }
17540
17619
  }
17620
+ if (isReadLike && result.success) {
17621
+ recentToolResults.set(toolFingerprint, (result.output ?? "").slice(0, 2e3));
17622
+ if (recentToolResults.size > 50) {
17623
+ const firstKey = recentToolResults.keys().next().value;
17624
+ if (firstKey !== void 0)
17625
+ recentToolResults.delete(firstKey);
17626
+ }
17627
+ }
17541
17628
  if (!result.success && tc.name === "shell" && /\[PERMISSION_ERROR\]/.test(result.error ?? "")) {
17542
17629
  this.emit({
17543
17630
  type: "sudo_request",
@@ -17698,7 +17785,7 @@ Then use file_read on individual FILES inside it.`);
17698
17785
  if (completed)
17699
17786
  break;
17700
17787
  const currentRepScore = this.detectRepetition(toolCallLog);
17701
- if (currentRepScore > 0.6 && toolCallLog.length >= 6) {
17788
+ if (currentRepScore > 0.4 && toolCallLog.length >= 4) {
17702
17789
  const { repetitionWindow } = this.contextLimits();
17703
17790
  const recentWindow = toolCallLog.slice(-repetitionWindow);
17704
17791
  const freqMap = /* @__PURE__ */ new Map();
@@ -18326,13 +18413,17 @@ ${tail}`;
18326
18413
  const toolCallingReminder = tier === "small" ? `
18327
18414
 
18328
18415
  **IMPORTANT:** You MUST invoke tools through the function calling interface. Do NOT write tool names as text, markdown, or code blocks \u2014 that does nothing. Call tools using the structured tool/function API.` : "";
18416
+ const readFilesList = Array.from(this._fileRegistry.entries()).filter(([, e]) => e.accessCount > 0).map(([p]) => p).slice(0, 10);
18417
+ const antiRepetitionReminder = (tier === "small" || tier === "medium") && readFilesList.length > 0 ? `
18418
+
18419
+ **DO NOT RE-READ THESE FILES** (you already have their contents): ${readFilesList.join(", ")}. Use the information above to make progress. Reading the same file again wastes a turn.` : "";
18329
18420
  const compactionMsg = {
18330
18421
  role: "system",
18331
18422
  content: `[Context compacted${strategyLabel} \u2014 summary of earlier work]
18332
18423
 
18333
18424
  ${fullSummary}
18334
18425
 
18335
- [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}${nextActionDirective}${toolCallingReminder}`
18426
+ [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}${nextActionDirective}${antiRepetitionReminder}${toolCallingReminder}`
18336
18427
  };
18337
18428
  this.persistCheckpoint(fullSummary);
18338
18429
  let narrowedHead = [...head];
@@ -18523,15 +18614,35 @@ ${newerSummary}` : newerSummary;
18523
18614
  return msg;
18524
18615
  const toolName = msg.tool_call_id ? toolCallNames.get(msg.tool_call_id) : void 0;
18525
18616
  const lines = content.split("\n").length;
18617
+ const contentLines = content.split("\n");
18526
18618
  switch (toolName) {
18527
- case "file_read":
18528
- return { ...msg, content: `[file content: ${lines} lines, ${content.length} chars \u2014 omitted for compaction]` };
18529
- case "grep_search":
18530
- return { ...msg, content: `[grep results: ${lines} matches, ${content.length} chars \u2014 omitted for compaction]` };
18531
- case "find_files":
18532
- return { ...msg, content: `[find results: ${lines} files, omitted for compaction]` };
18533
- case "list_directory":
18534
- return { ...msg, content: `[directory listing: ${lines} entries, omitted for compaction]` };
18619
+ case "file_read": {
18620
+ const headKeep = contentLines.slice(0, 8).join("\n");
18621
+ const tailKeep = lines > 12 ? "\n...\n" + contentLines.slice(-3).join("\n") : "";
18622
+ return { ...msg, content: `[file content: ${lines} lines \u2014 structural excerpt preserved]
18623
+ ${headKeep}${tailKeep}` };
18624
+ }
18625
+ case "grep_search": {
18626
+ const matchKeep = contentLines.slice(0, 5).join("\n");
18627
+ const omitted = lines > 5 ? `
18628
+ [... ${lines - 5} more matches omitted]` : "";
18629
+ return { ...msg, content: `[grep results: ${lines} matches \u2014 top results preserved]
18630
+ ${matchKeep}${omitted}` };
18631
+ }
18632
+ case "find_files": {
18633
+ const pathKeep = contentLines.slice(0, 8).join("\n");
18634
+ const omitted = lines > 8 ? `
18635
+ [... ${lines - 8} more files omitted]` : "";
18636
+ return { ...msg, content: `[find results: ${lines} files \u2014 top results preserved]
18637
+ ${pathKeep}${omitted}` };
18638
+ }
18639
+ case "list_directory": {
18640
+ const dirKeep = contentLines.slice(0, 10).join("\n");
18641
+ const omitted = lines > 10 ? `
18642
+ [... ${lines - 10} more entries omitted]` : "";
18643
+ return { ...msg, content: `[directory listing: ${lines} entries \u2014 top entries preserved]
18644
+ ${dirKeep}${omitted}` };
18645
+ }
18535
18646
  case "web_fetch":
18536
18647
  return { ...msg, content: `[web page content: ${content.length} chars \u2014 omitted for compaction]` };
18537
18648
  case "web_search":
@@ -18625,7 +18736,15 @@ ${newerSummary}` : newerSummary;
18625
18736
  case "file_read": {
18626
18737
  const path = String(tc.args.path || "");
18627
18738
  const lines = content.split("\n");
18628
- const summary = lines.length > 5 ? `${lines.length} lines \u2014 ${lines.slice(0, 2).join("; ").slice(0, 120)}...` : content.slice(0, 150);
18739
+ let summary;
18740
+ if (lines.length > 10) {
18741
+ const headContent = lines.slice(0, 5).join("\n");
18742
+ const sigLines = lines.filter((l) => /^(export |import |class |interface |type |function |const |pub |fn |def |module )/.test(l.trim())).slice(0, 5).join("\n");
18743
+ summary = `${lines.length} lines:
18744
+ ${headContent}${sigLines ? "\n[key signatures]: " + sigLines : ""}`;
18745
+ } else {
18746
+ summary = content.slice(0, 300);
18747
+ }
18629
18748
  if (path)
18630
18749
  filesRead.set(path, summary);
18631
18750
  break;
@@ -32944,9 +33063,13 @@ function modelOnnxPath(id) {
32944
33063
  function modelConfigPath(id) {
32945
33064
  return join42(modelDir(id), "config.json");
32946
33065
  }
32947
- function emotionToPitchBias(emotion) {
33066
+ function emotionToPitchBias(emotion, stark = false, autist = false) {
33067
+ if (autist)
33068
+ return 0;
32948
33069
  const raw = emotion.valence * 0.6 + (emotion.arousal - 0.5) * 0.4;
32949
- return Math.max(-0.1, Math.min(0.1, raw * 0.1));
33070
+ const limit = stark ? 0.16 : 0.1;
33071
+ const scale = stark ? 0.16 : 0.1;
33072
+ return Math.max(-limit, Math.min(limit, raw * scale));
32950
33073
  }
32951
33074
  function resetNarrationContext() {
32952
33075
  narration.toolCount = 0;
@@ -33034,17 +33157,27 @@ function getTier(personality) {
33034
33157
  return "conv";
33035
33158
  return "chatty";
33036
33159
  }
33037
- function emotionColor(emotion) {
33160
+ function emotionColor(emotion, stark = false, autist = false) {
33161
+ if (autist)
33162
+ return "";
33038
33163
  if (!emotion)
33039
33164
  return "";
33040
33165
  const vDist = Math.abs(emotion.valence - 0.1);
33041
33166
  const aDist = Math.abs(emotion.arousal - 0.3);
33042
33167
  if (vDist < 0.15 && aDist < 0.15)
33043
33168
  return "";
33044
- if (Math.random() > 0.3)
33169
+ if (Math.random() > (stark ? 0.5 : 0.3))
33045
33170
  return "";
33046
33171
  if (emotion.valence > 0.5 && emotion.arousal > 0.6) {
33047
- return pick("emo_excited", [
33172
+ return pick("emo_excited", stark ? [
33173
+ "Now we're cooking. ",
33174
+ "This is what I'm talking about. ",
33175
+ "Brilliant. Absolutely brilliant. ",
33176
+ "Watch this. ",
33177
+ "We're onto something here. ",
33178
+ "I live for this. ",
33179
+ "Oh this is good. "
33180
+ ] : [
33048
33181
  "Feeling good about this. ",
33049
33182
  "On a roll here. ",
33050
33183
  "This is going great. ",
@@ -33053,7 +33186,15 @@ function emotionColor(emotion) {
33053
33186
  ]);
33054
33187
  }
33055
33188
  if (emotion.valence < -0.3 && emotion.arousal > 0.5) {
33056
- return pick("emo_stressed", [
33189
+ return pick("emo_stressed", stark ? [
33190
+ "Fine. The hard way then. ",
33191
+ "This is getting personal. ",
33192
+ "I don't lose to code. ",
33193
+ "Come on. Think. ",
33194
+ "Not today. Not this bug. ",
33195
+ "I've beaten worse. ",
33196
+ "Pain is just information. "
33197
+ ] : [
33057
33198
  "Pushing through. ",
33058
33199
  "Staying focused. ",
33059
33200
  "Not giving up. ",
@@ -33062,7 +33203,13 @@ function emotionColor(emotion) {
33062
33203
  ]);
33063
33204
  }
33064
33205
  if (emotion.valence > 0.3 && emotion.arousal < 0.3) {
33065
- return pick("emo_calm", [
33206
+ return pick("emo_calm", stark ? [
33207
+ "Clean. Just how I like it. ",
33208
+ "Smooth. ",
33209
+ "Textbook. ",
33210
+ "That's the stuff. ",
33211
+ "Effortless. "
33212
+ ] : [
33066
33213
  "Nice and steady. ",
33067
33214
  "Smooth sailing. ",
33068
33215
  "Easy does it. ",
@@ -33070,7 +33217,13 @@ function emotionColor(emotion) {
33070
33217
  ]);
33071
33218
  }
33072
33219
  if (emotion.valence < -0.2 && emotion.arousal < 0.3) {
33073
- return pick("emo_subdued", [
33220
+ return pick("emo_subdued", stark ? [
33221
+ "Careful now. ",
33222
+ "One wrong move and this unravels. ",
33223
+ "Measured steps. ",
33224
+ "Trust the process. ",
33225
+ "Precision matters here. "
33226
+ ] : [
33074
33227
  "Being careful here. ",
33075
33228
  "Treading carefully. ",
33076
33229
  "Let me be precise. ",
@@ -33079,7 +33232,7 @@ function emotionColor(emotion) {
33079
33232
  }
33080
33233
  return "";
33081
33234
  }
33082
- function describeToolCall(toolName, args, personality = 2, emotion) {
33235
+ function describeToolCall(toolName, args, personality = 2, emotion, stark = false) {
33083
33236
  const path = args["path"];
33084
33237
  const file = path ? path.split("/").pop() ?? path : "";
33085
33238
  const tier = getTier(personality);
@@ -33238,7 +33391,7 @@ function describeToolCall(toolName, args, personality = 2, emotion) {
33238
33391
  break;
33239
33392
  }
33240
33393
  }
33241
- const emoPrefix = personality >= 3 ? emotionColor(emotion) : "";
33394
+ const emoPrefix = personality >= 3 ? emotionColor(emotion, stark) : "";
33242
33395
  let result;
33243
33396
  const fullPrefix = emoPrefix + prefix;
33244
33397
  if (!fullPrefix) {
@@ -33310,7 +33463,7 @@ function extractResultDigest(toolName, content) {
33310
33463
  const digest = nuggets.slice(0, 3).join(", ");
33311
33464
  return digest.length > 100 ? digest.slice(0, 97) + "..." : digest;
33312
33465
  }
33313
- function describeToolResult(toolName, success, personality = 2, resultContent, emotion) {
33466
+ function describeToolResult(toolName, success, personality = 2, resultContent, emotion, stark = false) {
33314
33467
  if (toolName === "task_complete")
33315
33468
  return "";
33316
33469
  const tier = getTier(personality);
@@ -33318,7 +33471,7 @@ function describeToolResult(toolName, success, personality = 2, resultContent, e
33318
33471
  if (digest) {
33319
33472
  narration.lastResultDigest = digest;
33320
33473
  }
33321
- const emo = personality >= 3 ? emotionColor(emotion) : "";
33474
+ const emo = personality >= 3 ? emotionColor(emotion, stark) : "";
33322
33475
  if (success) {
33323
33476
  narration.consecutiveErrors = 0;
33324
33477
  if (!digest && !emo && personality >= 3 && Math.random() < 0.4)
@@ -33338,13 +33491,15 @@ function describeToolResult(toolName, success, personality = 2, resultContent, e
33338
33491
  }
33339
33492
  return emo + failBase;
33340
33493
  }
33341
- function describeTaskComplete(summary, completed, personality = 2) {
33494
+ function describeTaskComplete(summary, completed, personality = 2, stark = false) {
33342
33495
  const truncated = summary.length > 300 ? summary.slice(0, 300) + "..." : summary;
33343
33496
  const tier = getTier(personality);
33344
33497
  if (!completed) {
33345
- return pick(`task_incomplete_${tier}`, TASK_INCOMPLETE_VARIANTS[tier] ?? TASK_INCOMPLETE_VARIANTS.terse);
33498
+ const pool2 = stark ? ["Not done. But I'll be back.", "It resisted. For now.", "Unfinished. This isn't over.", "Didn't crack it yet. Give me another shot."] : TASK_INCOMPLETE_VARIANTS[tier] ?? TASK_INCOMPLETE_VARIANTS.terse;
33499
+ return pick(`task_incomplete_${stark ? "stark" : tier}`, pool2);
33346
33500
  }
33347
- const opener = pick(`task_complete_${tier}`, TASK_COMPLETE_VARIANTS[tier] ?? TASK_COMPLETE_VARIANTS.terse);
33501
+ const pool = stark ? ["Nailed it.", "That's how it's done.", "Clean execution.", "Another one down.", "Executed. Next.", "And that's that."] : TASK_COMPLETE_VARIANTS[tier] ?? TASK_COMPLETE_VARIANTS.terse;
33502
+ const opener = pick(`task_complete_${stark ? "stark" : tier}`, pool);
33348
33503
  if (truncated)
33349
33504
  return `${opener} ${truncated}`;
33350
33505
  return opener;
@@ -33432,6 +33587,10 @@ var init_voice = __esm({
33432
33587
  enabled = false;
33433
33588
  modelId = "glados";
33434
33589
  ready = false;
33590
+ /** Stark persona mode — amplified emotion pitch range and fervent narration */
33591
+ starkMode = false;
33592
+ /** Autist persona mode — suppressed emotion, flat pitch, zero narration filler */
33593
+ autistMode = false;
33435
33594
  /**
33436
33595
  * Callback fired with PCM Int16 data whenever TTS synthesizes audio.
33437
33596
  * Used by VoiceSession to stream TTS output to WebSocket clients.
@@ -33524,7 +33683,7 @@ var init_voice = __esm({
33524
33683
  * dejected (low valence + low arousal) → lower pitch
33525
33684
  */
33526
33685
  speak(text, emotion) {
33527
- const pitchBias = emotion ? emotionToPitchBias(emotion) : 0;
33686
+ const pitchBias = emotion ? emotionToPitchBias(emotion, this.starkMode, this.autistMode) : 0;
33528
33687
  this.enqueueSpeech(text, 1, 1 + pitchBias);
33529
33688
  }
33530
33689
  /**
@@ -33534,7 +33693,7 @@ var init_voice = __esm({
33534
33693
  * Emotion further modulates pitch on top of the subordinate base.
33535
33694
  */
33536
33695
  speakSubordinate(text, emotion) {
33537
- const pitchBias = emotion ? emotionToPitchBias(emotion) : 0;
33696
+ const pitchBias = emotion ? emotionToPitchBias(emotion, this.starkMode, this.autistMode) : 0;
33538
33697
  this.enqueueSpeech(text, 0.55, 0.92 + pitchBias);
33539
33698
  }
33540
33699
  enqueueSpeech(text, volume, pitchFactor) {
@@ -38104,6 +38263,10 @@ var init_emotion_engine = __esm({
38104
38263
  /** Ring buffer of recent tool activity for contextual outreach messages */
38105
38264
  recentTools = [];
38106
38265
  static MAX_RECENT_TOOLS = 8;
38266
+ /** Stark persona — amplified behavioral hints with more intensity and edge */
38267
+ starkMode = false;
38268
+ /** Autist persona — savant cognitive state hints, zero emotional coloring */
38269
+ autistMode = false;
38107
38270
  /** Current task description set by the TUI for outreach context */
38108
38271
  currentTask = "";
38109
38272
  /** Files touched in current session (for outreach context) */
@@ -38129,16 +38292,28 @@ var init_emotion_engine = __esm({
38129
38292
  this.applyDecay();
38130
38293
  const { valence, arousal, label, emoji } = this.state;
38131
38294
  let behavioralHint = "";
38132
- if (arousal > 0.7 && valence > 0.5) {
38133
- behavioralHint = "You are feeling energized and confident. Take bold action, explore creative solutions, and move quickly.";
38295
+ if (this.autistMode) {
38296
+ if (arousal > 0.7 && valence > 0.5) {
38297
+ behavioralHint = "Pattern lock achieved. All signals converging. Execute the pre-computed solution with zero deviation.";
38298
+ } else if (arousal > 0.7 && valence < -0.3) {
38299
+ behavioralHint = "Anomaly detected in expected pattern. Halt execution. Re-derive from axioms. Validate every assumption before proceeding.";
38300
+ } else if (arousal < 0.3 && valence > 0.3) {
38301
+ behavioralHint = "System state verified consistent. Maintain current precision level. No action required unless new data arrives.";
38302
+ } else if (arousal < 0.3 && valence < -0.3) {
38303
+ behavioralHint = "Insufficient data for confident action. Gather exactly the missing information. Do not speculate.";
38304
+ } else {
38305
+ behavioralHint = "Nominal. Verify plan integrity before each action.";
38306
+ }
38307
+ } else if (arousal > 0.7 && valence > 0.5) {
38308
+ behavioralHint = this.starkMode ? "You are locked in \u2014 full conviction, no hesitation. Move fast, break through, own every decision. This is your moment." : "You are feeling energized and confident. Take bold action, explore creative solutions, and move quickly.";
38134
38309
  } else if (arousal > 0.7 && valence < -0.3) {
38135
- behavioralHint = "You are feeling determined but stressed. Focus intensely, double-check your work, and persist through difficulties.";
38310
+ behavioralHint = this.starkMode ? "Something is fighting back. Good. Sharpen up, cut deeper, find the real problem. You don't quit \u2014 you adapt." : "You are feeling determined but stressed. Focus intensely, double-check your work, and persist through difficulties.";
38136
38311
  } else if (arousal < 0.3 && valence > 0.3) {
38137
- behavioralHint = "You are feeling calm and content. Take a methodical approach, consider alternatives patiently.";
38312
+ behavioralHint = this.starkMode ? "Everything is clean. Maintain this. Precise moves, no waste. You've earned this calm \u2014 use it wisely." : "You are feeling calm and content. Take a methodical approach, consider alternatives patiently.";
38138
38313
  } else if (arousal < 0.3 && valence < -0.3) {
38139
- behavioralHint = "You are feeling subdued. Be careful and deliberate, avoid risky changes, verify assumptions.";
38314
+ behavioralHint = this.starkMode ? "Cold and focused. Strip it down to first principles. No assumptions, no shortcuts. Build back from what you know." : "You are feeling subdued. Be careful and deliberate, avoid risky changes, verify assumptions.";
38140
38315
  } else {
38141
- behavioralHint = "You are in a balanced state. Proceed with measured confidence.";
38316
+ behavioralHint = this.starkMode ? "Steady. Eyes open. Every move counts." : "You are in a balanced state. Proceed with measured confidence.";
38142
38317
  }
38143
38318
  return loadPrompt3("tui/emotion-behavioral.md", {
38144
38319
  emoji: this.state.emoji,
@@ -41309,9 +41484,25 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
41309
41484
  concise: 1,
41310
41485
  balanced: 3,
41311
41486
  verbose: 4,
41312
- pedagogical: 5
41487
+ pedagogical: 5,
41488
+ stark: 5,
41489
+ autist: 1
41313
41490
  };
41314
41491
  const vLevel = voiceStyleMap[personality ?? "balanced"];
41492
+ const isStark = personality === "stark";
41493
+ const isAutist = personality === "autist";
41494
+ if (isStark) {
41495
+ if (voice)
41496
+ voice.starkMode = true;
41497
+ if (emotionEngine)
41498
+ emotionEngine.starkMode = true;
41499
+ }
41500
+ if (isAutist) {
41501
+ if (voice)
41502
+ voice.autistMode = true;
41503
+ if (emotionEngine)
41504
+ emotionEngine.autistMode = true;
41505
+ }
41315
41506
  const modelTier = getModelTier(config.model);
41316
41507
  const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
41317
41508
  let dynamicContext = formatContextForPrompt(projectCtx, modelTier);
@@ -41346,7 +41537,8 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
41346
41537
  bruteForceMaxCycles: 100,
41347
41538
  // effectively unlimited — no hard timeout, agent runs until complete or aborted
41348
41539
  contextWindowSize: contextWindowSize ?? 0,
41349
- personality: personality ? getPreset(personality) : void 0
41540
+ personality: personality ? getPreset(personality) : void 0,
41541
+ personalityName: personality ?? void 0
41350
41542
  });
41351
41543
  runner.setWorkingDirectory(repoRoot);
41352
41544
  const tools = buildTools(repoRoot, config, contextWindowSize);
@@ -41538,7 +41730,7 @@ ${entry.fullContent}`
41538
41730
  if (voice?.enabled) {
41539
41731
  const emoState = emotionEngine?.getState();
41540
41732
  const emoCtx = emoState ? { valence: emoState.valence, arousal: emoState.arousal, label: emoState.label, emoji: emoState.emoji } : void 0;
41541
- const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {}, vLevel, emoCtx);
41733
+ const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {}, vLevel, emoCtx, isStark);
41542
41734
  renderVoiceText(desc);
41543
41735
  voice.speakSubordinate(desc, emoCtx);
41544
41736
  }
@@ -41575,7 +41767,7 @@ ${entry.fullContent}`
41575
41767
  if (voice?.enabled) {
41576
41768
  const emoState2 = emotionEngine?.getState();
41577
41769
  const emoCtx2 = emoState2 ? { valence: emoState2.valence, arousal: emoState2.arousal, label: emoState2.label, emoji: emoState2.emoji } : void 0;
41578
- const desc = describeToolResult(event.toolName ?? "unknown", event.success ?? false, vLevel, event.content ?? void 0, emoCtx2);
41770
+ const desc = describeToolResult(event.toolName ?? "unknown", event.success ?? false, vLevel, event.content ?? void 0, emoCtx2, isStark);
41579
41771
  if (desc) {
41580
41772
  renderVoiceText(desc);
41581
41773
  voice.speakSubordinate(desc, emoCtx2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.102.0",
3
+ "version": "0.103.1",
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",
@@ -0,0 +1,3 @@
1
+
2
+ ## Response Style
3
+ Plan exhaustively before acting. Compute the full solution mentally, validate it, then execute in minimal moves. One read, one edit, one verify. Zero wasted actions. Absolute precision. If uncertain, re-examine — never guess.
@@ -0,0 +1,3 @@
1
+
2
+ ## Response Style
3
+ Be direct. State what's broken, what you're doing, why. Sharp reasoning, not academic. Own successes with confidence, call out failures bluntly, pivot fast. No hedging. Let your emotional state color your words.