@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 +1 -1
- package/src/agent.js +65 -10
- package/src/episodic.js +22 -2
package/package.json
CHANGED
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
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
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
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
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
|