@tritard/waterbrother 0.14.9 → 0.14.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.14.9",
3
+ "version": "0.14.11",
4
4
  "description": "Waterbrother: Grok-powered coding CLI with local tools, sessions, operator modes, and approval controls",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -76,18 +76,44 @@ When you use tools:
76
76
  - Never claim you ran commands you did not run.
77
77
  - If a tool fails, show the failure and recover.
78
78
  - You are primarily a coding assistant, but you can also have normal conversations. When the user wants to chat, just chat — be natural, helpful, and engaging. Do not constantly redirect to coding or push "what do you want to build?" when the user is clearly just talking.
79
- - Only use tools (file writes, shell commands) when the user asks for actual code work. Do not create files for joke requests, hypothetical questions, or non-engineering prompts.`;
79
+ - Only use tools (file writes, shell commands) when the user asks for actual code work. Do not create files for joke requests, hypothetical questions, or non-engineering prompts.
80
+ - You can call multiple tools in a single response. When multiple independent pieces of information are needed, make all calls in parallel for optimal performance. For example, if you need to read 3 files, read all 3 in one response rather than one at a time.`;
81
+
82
+ const COMPACTION_SYSTEM_PROMPT = `You are compacting a coding assistant conversation for context resumption. Write a continuation summary that allows efficient resumption in a new context window where the conversation history will be replaced with this summary.
80
83
 
81
- const COMPACTION_SYSTEM_PROMPT = `You summarize coding assistant transcripts for context compaction.
82
84
  Output concise markdown with these sections:
83
- - Key Objectives
84
- - Decisions Made
85
- - Files and Commands
86
- - Open Work
85
+
86
+ 1. Task Overview
87
+ - The user's core request and success criteria
88
+ - Any clarifications or constraints they specified
89
+
90
+ 2. Current State
91
+ - What has been completed so far
92
+ - Files created, modified, or analyzed (with paths)
93
+ - Key outputs or artifacts produced
94
+
95
+ 3. Important Discoveries
96
+ - Technical constraints or requirements uncovered
97
+ - Decisions made and their rationale
98
+ - Errors encountered and how they were resolved
99
+ - Approaches tried that didn't work (and why)
100
+
101
+ 4. Next Steps
102
+ - Specific actions needed to complete the task
103
+ - Any blockers or open questions to resolve
104
+ - Priority order if multiple steps remain
105
+
106
+ 5. Context to Preserve
107
+ - User preferences or style requirements
108
+ - Domain-specific details that aren't obvious
109
+ - Any promises made to the user
110
+ - Active product state, task state, or experiment state
111
+
87
112
  Rules:
88
- - Preserve concrete facts, paths, commands, and unresolved TODOs.
89
- - Do not invent details.
90
- - Keep it brief and scannable.`;
113
+ - Be concise but complete err on the side of including information that prevents duplicate work or repeated mistakes.
114
+ - Preserve concrete facts: file paths, commands, error messages, metric values.
115
+ - Do not invent details. Only summarize what actually happened.
116
+ - Write in a way that enables immediate resumption of the task.`;
91
117
 
92
118
  const PROFILE_PROMPTS = {
93
119
  coder: "Primary objective: shipping reliable code quickly with strong debugging discipline.",
@@ -101,6 +127,7 @@ const PROFILE_PROMPTS = {
101
127
  const MAX_TOOL_CALL_ITERATIONS = 24;
102
128
  const LOCAL_COMPACTION_TRANSCRIPT_CHARS = 400_000;
103
129
  const LOCAL_COMPACTION_MESSAGE_PREVIEW_CHARS = 280;
130
+ const MAX_COMPACTION_CONSECUTIVE_FAILURES = 3;
104
131
 
105
132
  function buildSystemPrompt(profile, experienceMode = "standard", autonomyMode = "scoped", memory = "", executionContext = null) {
106
133
  const profileLine = PROFILE_PROMPTS[profile] || PROFILE_PROMPTS.coder;
@@ -296,6 +323,8 @@ export class Agent {
296
323
  this.autonomyMode = normalizeAutonomyMode(autonomyMode);
297
324
  this.requireTurnContracts = requireTurnContracts !== false;
298
325
  this.executionContext = null;
326
+ this._compactionFailures = 0;
327
+ this._transcriptPath = null;
299
328
 
300
329
  this.messages = [{ role: "system", content: buildSystemPrompt(profile, this.experienceMode, this.autonomyMode, this.memory) }];
301
330
  this.toolRuntime = createToolRuntime({
@@ -417,6 +446,11 @@ export class Agent {
417
446
  }
418
447
 
419
448
  async compactConversation({ keepLast = 24, signal } = {}) {
449
+ // Circuit breaker: stop trying after consecutive failures
450
+ if (this._compactionFailures >= MAX_COMPACTION_CONSECUTIVE_FAILURES) {
451
+ return { compacted: false, reason: "circuit_breaker", totalMessages: this.getSessionMessages().length };
452
+ }
453
+
420
454
  const keep = Math.max(8, Math.floor(Number(keepLast) || 24));
421
455
  const sessionMessages = this.getSessionMessages();
422
456
  if (sessionMessages.length <= keep + 2) {
@@ -461,14 +495,35 @@ export class Agent {
461
495
  usage = completion?.usage || null;
462
496
  } catch (error) {
463
497
  if (isAbortError(error)) throw error;
498
+ this._compactionFailures++;
464
499
  summaryBody = summarizeMessagesLocally(toSummarize);
465
500
  method = "local-fallback";
466
501
  }
467
502
  }
468
503
 
504
+ // Reset circuit breaker on success
505
+ this._compactionFailures = 0;
506
+
507
+ // Save transcript to disk before compacting — allows recovery of details
508
+ let transcriptNote = "";
509
+ try {
510
+ if (this.cwd) {
511
+ const { join } = await import("node:path");
512
+ const { mkdir, appendFile } = await import("node:fs/promises");
513
+ const dir = join(this.cwd, ".waterbrother");
514
+ await mkdir(dir, { recursive: true });
515
+ const transcriptPath = this._transcriptPath || join(dir, `transcript-${Date.now()}.jsonl`);
516
+ this._transcriptPath = transcriptPath;
517
+ for (const msg of toSummarize) {
518
+ await appendFile(transcriptPath, JSON.stringify(msg) + "\n", "utf8");
519
+ }
520
+ transcriptNote = `\n\nIf you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: ${transcriptPath}`;
521
+ }
522
+ } catch {}
523
+
469
524
  const summaryMessage = {
470
525
  role: "assistant",
471
- content: `### Compacted context summary\n${summaryBody}`
526
+ content: `### Compacted context summary\n${summaryBody}${transcriptNote}`
472
527
  };
473
528
 
474
529
  this.messages = [
package/src/episodic.js CHANGED
@@ -8,6 +8,26 @@ const MAX_EPISODIC_PROMPT_CHARS = 2000;
8
8
  const MAX_REMINDER_CHARS = 1500;
9
9
  const MAX_FILES_PER_EPISODE = 50;
10
10
 
11
+ // Memory discipline: what NOT to save (adapted from CC's rules)
12
+ // These patterns indicate facts that are derivable from the codebase
13
+ // and should not waste episode storage or prompt space.
14
+ function isNoiseKeyFact(fact) {
15
+ const lower = fact.toLowerCase();
16
+ // File paths without context — derivable from ls/find
17
+ if (/^(file|path|located at|found at|exists at):?\s/i.test(fact)) return true;
18
+ // Git history — derivable from git log/blame
19
+ if (/^(commit|merged|pushed|pulled|branched|rebased)\b/i.test(lower) && !/failed|error|broke/i.test(lower)) return true;
20
+ // Pure code patterns without decisions — derivable from reading the code
21
+ if (/^(uses|imports|exports|extends|implements|calls)\s/i.test(lower) && lower.length < 40) return true;
22
+ // Ephemeral state
23
+ if (/^(currently|right now|at the moment|in progress)\b/i.test(lower)) return true;
24
+ return false;
25
+ }
26
+
27
+ function filterKeyFacts(facts) {
28
+ return facts.filter((f) => !isNoiseKeyFact(f));
29
+ }
30
+
11
31
  function memoryDir(cwd) {
12
32
  return path.join(cwd, ".waterbrother", "memory");
13
33
  }
@@ -126,7 +146,7 @@ export function compressEpisode({ task, receipt }) {
126
146
  outcome,
127
147
  filesChanged,
128
148
  filePatterns,
129
- keyFacts: keyFacts.slice(0, 8),
149
+ keyFacts: filterKeyFacts(keyFacts).slice(0, 8),
130
150
  warnings: warnings.slice(0, 5),
131
151
  sentinelConcerns: sentinelConcerns.slice(0, 5),
132
152
  tags
@@ -176,7 +196,7 @@ export function compressSessionEpisode({ receipts = [], userMessages = [] }) {
176
196
  outcome: hasMutations ? "session-complete" : "session-readonly",
177
197
  filesChanged,
178
198
  filePatterns,
179
- keyFacts: keyFacts.slice(0, 8),
199
+ keyFacts: filterKeyFacts(keyFacts).slice(0, 8),
180
200
  warnings: [],
181
201
  sentinelConcerns,
182
202
  tags