@bubblebrain-ai/bubble 0.0.40 → 0.0.42
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/dist/agent/internal-reminder-sanitizer.d.ts +8 -0
- package/dist/agent/internal-reminder-sanitizer.js +10 -0
- package/dist/agent.d.ts +9 -0
- package/dist/agent.js +20 -0
- package/dist/context/compact.d.ts +15 -0
- package/dist/context/compact.js +50 -2
- package/dist/context/llm-compactor.js +17 -2
- package/dist/main.js +2 -0
- package/dist/orchestrator/default-hooks.js +26 -2
- package/dist/orchestrator/hooks.d.ts +2 -0
- package/dist/print-output.d.ts +13 -0
- package/dist/print-output.js +2 -0
- package/dist/prompt/reminders.d.ts +1 -0
- package/dist/prompt/reminders.js +7 -0
- package/dist/prompt/runtime.js +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { AssistantProviderMetadata } from "../types.js";
|
|
2
|
+
/** Prefix shared by every internal reminder/context block this module emits. */
|
|
3
|
+
export declare const INTERNAL_BLOCK_PREFIX = "<bubble_internal_";
|
|
4
|
+
/**
|
|
5
|
+
* True when the text is an internal reminder/context block (e.g. a meta
|
|
6
|
+
* message that the projector re-rolled into user role) rather than something
|
|
7
|
+
* the user actually typed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isInternalBlockContent(text: string): boolean;
|
|
2
10
|
export declare function formatInternalReminderBlock(kind: string, content: string): string;
|
|
3
11
|
export declare function formatInternalContextBlock(kind: string, content: string): string;
|
|
4
12
|
export declare function sanitizeInternalReminderBlocks(text: string): string;
|
|
@@ -27,6 +27,16 @@ const LEGACY_REMINDER_END_PHRASES = [
|
|
|
27
27
|
"- Skip the \"investigate the codebase\" step that applies to larger changes.",
|
|
28
28
|
"Do not put the final answer only in hidden reasoning.",
|
|
29
29
|
];
|
|
30
|
+
/** Prefix shared by every internal reminder/context block this module emits. */
|
|
31
|
+
export const INTERNAL_BLOCK_PREFIX = "<bubble_internal_";
|
|
32
|
+
/**
|
|
33
|
+
* True when the text is an internal reminder/context block (e.g. a meta
|
|
34
|
+
* message that the projector re-rolled into user role) rather than something
|
|
35
|
+
* the user actually typed.
|
|
36
|
+
*/
|
|
37
|
+
export function isInternalBlockContent(text) {
|
|
38
|
+
return text.trimStart().startsWith(INTERNAL_BLOCK_PREFIX);
|
|
39
|
+
}
|
|
30
40
|
export function formatInternalReminderBlock(kind, content) {
|
|
31
41
|
return formatInternalBlock("reminder", kind, content);
|
|
32
42
|
}
|
package/dist/agent.d.ts
CHANGED
|
@@ -175,6 +175,7 @@ export declare class Agent {
|
|
|
175
175
|
private readonly subagentUpdateWakers;
|
|
176
176
|
private lastInputTokens;
|
|
177
177
|
private lastAnchorMessageCount;
|
|
178
|
+
private compactionStats;
|
|
178
179
|
constructor(options: AgentOptions);
|
|
179
180
|
/**
|
|
180
181
|
* Re-inject the deferred-tools advertisement. Hosts that reassign
|
|
@@ -226,6 +227,7 @@ export declare class Agent {
|
|
|
226
227
|
private getActiveToolEntries;
|
|
227
228
|
injectSystemReminder(content: string): void;
|
|
228
229
|
injectModeReminder(): void;
|
|
230
|
+
get role(): "parent" | "subagent";
|
|
229
231
|
get model(): string;
|
|
230
232
|
set model(value: string);
|
|
231
233
|
get providerId(): string;
|
|
@@ -256,6 +258,13 @@ export declare class Agent {
|
|
|
256
258
|
private recoverFromOverflow;
|
|
257
259
|
compactResidentHistory(): void;
|
|
258
260
|
private maybeCompactWithLLM;
|
|
261
|
+
/** Snapshot of how often each compaction path rewrote history this run. */
|
|
262
|
+
getCompactionStats(): {
|
|
263
|
+
resident: number;
|
|
264
|
+
llm: number;
|
|
265
|
+
overflow: number;
|
|
266
|
+
droppedMessages: number;
|
|
267
|
+
};
|
|
259
268
|
/**
|
|
260
269
|
* Stream a 9-section handoff summary of `oldMessages` from the session model.
|
|
261
270
|
* Powers the manual `/compact` command: streaming (rather than `complete()`)
|
package/dist/agent.js
CHANGED
|
@@ -172,6 +172,12 @@ export class Agent {
|
|
|
172
172
|
subagentUpdateWakers = new Set();
|
|
173
173
|
lastInputTokens = null;
|
|
174
174
|
lastAnchorMessageCount = null;
|
|
175
|
+
// How often each compaction path actually rewrote history this run.
|
|
176
|
+
// Surfaced via getCompactionStats() for print-mode telemetry: without it,
|
|
177
|
+
// "did the model ever stop seeing the original instruction?" is
|
|
178
|
+
// unanswerable from the outside (only counts real compactions, not
|
|
179
|
+
// byte-saving projection rewrites).
|
|
180
|
+
compactionStats = { resident: 0, llm: 0, overflow: 0, droppedMessages: 0 };
|
|
175
181
|
constructor(options) {
|
|
176
182
|
this.provider = options.provider;
|
|
177
183
|
this.sessionID = options.sessionID;
|
|
@@ -422,6 +428,9 @@ export class Agent {
|
|
|
422
428
|
}
|
|
423
429
|
this.injectSystemReminder(reminder);
|
|
424
430
|
}
|
|
431
|
+
get role() {
|
|
432
|
+
return this.agentRole;
|
|
433
|
+
}
|
|
425
434
|
get model() {
|
|
426
435
|
return this._model;
|
|
427
436
|
}
|
|
@@ -992,6 +1001,9 @@ export class Agent {
|
|
|
992
1001
|
}
|
|
993
1002
|
const droppedMessages = await this.recoverFromOverflow(consecutiveOverflowRecoveries);
|
|
994
1003
|
consecutiveOverflowRecoveries += 1;
|
|
1004
|
+
this.compactionStats.overflow += 1;
|
|
1005
|
+
this.compactionStats.droppedMessages += droppedMessages;
|
|
1006
|
+
traceEvent("compaction_fired", { path: "overflow", droppedMessages });
|
|
995
1007
|
yield emit({ type: "context_recovered", droppedMessages, reason: "overflow" });
|
|
996
1008
|
continue;
|
|
997
1009
|
}
|
|
@@ -1507,10 +1519,16 @@ export class Agent {
|
|
|
1507
1519
|
this.lastInputTokens = null;
|
|
1508
1520
|
this.lastAnchorMessageCount = null;
|
|
1509
1521
|
this.fileStateTracker?.invalidateReadHistory();
|
|
1522
|
+
this.compactionStats.llm += 1;
|
|
1523
|
+
traceEvent("compaction_fired", { path: "llm" });
|
|
1510
1524
|
}
|
|
1511
1525
|
// If LLM compaction failed for any reason, leave this.messages alone —
|
|
1512
1526
|
// the projector's algorithmic budgeted-mode passes will still try.
|
|
1513
1527
|
}
|
|
1528
|
+
/** Snapshot of how often each compaction path rewrote history this run. */
|
|
1529
|
+
getCompactionStats() {
|
|
1530
|
+
return { ...this.compactionStats };
|
|
1531
|
+
}
|
|
1514
1532
|
/**
|
|
1515
1533
|
* Stream a 9-section handoff summary of `oldMessages` from the session model.
|
|
1516
1534
|
* Powers the manual `/compact` command: streaming (rather than `complete()`)
|
|
@@ -2537,6 +2555,8 @@ export class Agent {
|
|
|
2537
2555
|
const compacted = compactMessages(candidate, { keepRecentTurns });
|
|
2538
2556
|
if (compacted.compacted && compacted.messages) {
|
|
2539
2557
|
candidate = compacted.messages;
|
|
2558
|
+
this.compactionStats.resident += 1;
|
|
2559
|
+
traceEvent("compaction_fired", { path: "resident", droppedEntries: compacted.droppedEntries });
|
|
2540
2560
|
}
|
|
2541
2561
|
}
|
|
2542
2562
|
const afterChars = estimateResidentChars(candidate);
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import type { Message } from "../types.js";
|
|
2
2
|
import type { SessionLogEntry } from "../session-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Compaction must never summarize away what the user actually asked for:
|
|
5
|
+
* requirements that appear late in a long instruction ("commit when done",
|
|
6
|
+
* "write the report in French") would otherwise vanish once the heuristic
|
|
7
|
+
* summary truncates the goal line. Messages larger than this cap are exempt
|
|
8
|
+
* from pinning (e.g. a pasted megabyte of logs as the opening message) —
|
|
9
|
+
* summarization is the lesser evil there.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PINNED_INSTRUCTION_MAX_CHARS = 8192;
|
|
12
|
+
/**
|
|
13
|
+
* Index of the first message the user actually wrote: user-role, not an
|
|
14
|
+
* internal reminder/context block re-rolled into user role by the projector,
|
|
15
|
+
* and small enough to keep verbatim.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findFirstRealUserIndex(messages: Message[]): number;
|
|
3
18
|
export interface CompactOptions {
|
|
4
19
|
keepRecentTurns?: number;
|
|
5
20
|
maxSummaryItems?: number;
|
package/dist/context/compact.js
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
import { isInternalBlockContent } from "../agent/internal-reminder-sanitizer.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compaction must never summarize away what the user actually asked for:
|
|
4
|
+
* requirements that appear late in a long instruction ("commit when done",
|
|
5
|
+
* "write the report in French") would otherwise vanish once the heuristic
|
|
6
|
+
* summary truncates the goal line. Messages larger than this cap are exempt
|
|
7
|
+
* from pinning (e.g. a pasted megabyte of logs as the opening message) —
|
|
8
|
+
* summarization is the lesser evil there.
|
|
9
|
+
*/
|
|
10
|
+
export const PINNED_INSTRUCTION_MAX_CHARS = 8192;
|
|
11
|
+
function userMessageText(message) {
|
|
12
|
+
if (typeof message.content === "string")
|
|
13
|
+
return message.content;
|
|
14
|
+
if (Array.isArray(message.content)) {
|
|
15
|
+
return message.content
|
|
16
|
+
.filter((part) => part.type === "text")
|
|
17
|
+
.map((part) => part.text)
|
|
18
|
+
.join(" ");
|
|
19
|
+
}
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Index of the first message the user actually wrote: user-role, not an
|
|
24
|
+
* internal reminder/context block re-rolled into user role by the projector,
|
|
25
|
+
* and small enough to keep verbatim.
|
|
26
|
+
*/
|
|
27
|
+
export function findFirstRealUserIndex(messages) {
|
|
28
|
+
for (let index = 0; index < messages.length; index++) {
|
|
29
|
+
const message = messages[index];
|
|
30
|
+
if (message.role !== "user")
|
|
31
|
+
continue;
|
|
32
|
+
const text = userMessageText(message);
|
|
33
|
+
if (!text.trim() || isInternalBlockContent(text))
|
|
34
|
+
continue;
|
|
35
|
+
if (text.length > PINNED_INSTRUCTION_MAX_CHARS)
|
|
36
|
+
return -1;
|
|
37
|
+
return index;
|
|
38
|
+
}
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
1
41
|
export function planSessionCompaction(entries, options = {}) {
|
|
2
42
|
const keepRecentTurns = options.keepRecentTurns ?? 2;
|
|
3
43
|
const metadataEntries = entries.filter((entry) => entry.type === "metadata");
|
|
@@ -74,12 +114,20 @@ export function compactMessages(messages, options = {}) {
|
|
|
74
114
|
}
|
|
75
115
|
const oldMessages = conversationalMessages.slice(0, keepStartIndex);
|
|
76
116
|
const keptMessages = conversationalMessages.slice(keepStartIndex);
|
|
77
|
-
|
|
117
|
+
// Pin the original instruction: it stays verbatim above the summary instead
|
|
118
|
+
// of being crushed into the summary's 140-char goal line.
|
|
119
|
+
const pinnedIndex = findFirstRealUserIndex(oldMessages);
|
|
120
|
+
const pinnedMessage = pinnedIndex >= 0 ? oldMessages[pinnedIndex] : undefined;
|
|
121
|
+
const summaryInput = pinnedIndex >= 0
|
|
122
|
+
? oldMessages.filter((_, index) => index !== pinnedIndex)
|
|
123
|
+
: oldMessages;
|
|
124
|
+
const summary = buildMessageSummary(summaryInput, maxSummaryItems);
|
|
78
125
|
if (!summary) {
|
|
79
126
|
return { compacted: false };
|
|
80
127
|
}
|
|
81
128
|
const compactedMessages = [
|
|
82
129
|
...preservedContextMessages.map((message) => cloneMessage(message)),
|
|
130
|
+
...(pinnedMessage ? [cloneMessage(pinnedMessage)] : []),
|
|
83
131
|
{
|
|
84
132
|
role: "system",
|
|
85
133
|
content: `Previous conversation summary:\n${summary}`,
|
|
@@ -90,7 +138,7 @@ export function compactMessages(messages, options = {}) {
|
|
|
90
138
|
compacted: true,
|
|
91
139
|
summary,
|
|
92
140
|
messages: compactedMessages,
|
|
93
|
-
droppedEntries:
|
|
141
|
+
droppedEntries: summaryInput.length,
|
|
94
142
|
};
|
|
95
143
|
}
|
|
96
144
|
export function compactCurrentTurnToolGroups(messages, options = {}) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// Failure modes are explicit: returns { compacted: false, reason } so the
|
|
11
11
|
// caller can fall back to algorithmic compaction without an exception.
|
|
12
12
|
import { estimateContextTokens } from "./budget.js";
|
|
13
|
+
import { findFirstRealUserIndex } from "./compact.js";
|
|
13
14
|
export const LLM_COMPACTION_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
|
|
14
15
|
|
|
15
16
|
Include:
|
|
@@ -40,9 +41,21 @@ export async function compactWithLLM(messages, options) {
|
|
|
40
41
|
// priorTurns: everything from earlier user turns (multi-turn case)
|
|
41
42
|
// lastUser: the user's current ask (always kept verbatim)
|
|
42
43
|
// currentTurn: the assistant + tool groups produced in response so far
|
|
44
|
+
//
|
|
45
|
+
// The FIRST real user message (the original instruction) is pinned verbatim
|
|
46
|
+
// too: after meta reminders get re-rolled into user role by the projector,
|
|
47
|
+
// "last user message" often points at a reminder, not the instruction — and
|
|
48
|
+
// late requirements in a long instruction must survive compaction.
|
|
43
49
|
const priorTurns = body.slice(0, lastUserIndex);
|
|
44
50
|
const lastUser = body[lastUserIndex];
|
|
45
51
|
const currentTurn = body.slice(lastUserIndex + 1);
|
|
52
|
+
const firstRealUserIndex = findFirstRealUserIndex(body);
|
|
53
|
+
const pinnedFirstUser = firstRealUserIndex >= 0 && firstRealUserIndex < lastUserIndex
|
|
54
|
+
? body[firstRealUserIndex]
|
|
55
|
+
: undefined;
|
|
56
|
+
const summarizablePriorTurns = pinnedFirstUser
|
|
57
|
+
? priorTurns.filter((message) => message !== pinnedFirstUser)
|
|
58
|
+
: priorTurns;
|
|
46
59
|
const groups = [];
|
|
47
60
|
let active = null;
|
|
48
61
|
for (const msg of currentTurn) {
|
|
@@ -61,9 +74,10 @@ export async function compactWithLLM(messages, options) {
|
|
|
61
74
|
const evictedGroups = groups.slice(0, groups.length - keptGroupCount);
|
|
62
75
|
const keptGroups = groups.slice(groups.length - keptGroupCount);
|
|
63
76
|
// What we'll send to the model to summarize: prior turns + the older groups
|
|
64
|
-
// in the current turn (everything we're about to evict).
|
|
77
|
+
// in the current turn (everything we're about to evict). The pinned first
|
|
78
|
+
// instruction is excluded — it survives verbatim, not as summary fodder.
|
|
65
79
|
const toSummarize = [
|
|
66
|
-
...
|
|
80
|
+
...summarizablePriorTurns,
|
|
67
81
|
...evictedGroups.flatMap((g) => [g.assistant, ...g.toolResults]),
|
|
68
82
|
];
|
|
69
83
|
if (toSummarize.length === 0) {
|
|
@@ -105,6 +119,7 @@ export async function compactWithLLM(messages, options) {
|
|
|
105
119
|
}
|
|
106
120
|
const compacted = [
|
|
107
121
|
...preserved.map(cloneMessage),
|
|
122
|
+
...(pinnedFirstUser ? [cloneMessage(pinnedFirstUser)] : []),
|
|
108
123
|
{
|
|
109
124
|
role: "user",
|
|
110
125
|
content: `${LLM_SUMMARY_PREFIX}\n${summaryText.trim()}`,
|
package/dist/main.js
CHANGED
|
@@ -616,6 +616,7 @@ async function main() {
|
|
|
616
616
|
process.stdout.write(formatPrintJson({
|
|
617
617
|
summary: collector.summary(),
|
|
618
618
|
sessionId: printSessionId,
|
|
619
|
+
compaction: agent.getCompactionStats(),
|
|
619
620
|
}) + "\n");
|
|
620
621
|
}
|
|
621
622
|
else {
|
|
@@ -631,6 +632,7 @@ async function main() {
|
|
|
631
632
|
message: error instanceof Error ? error.message : String(error),
|
|
632
633
|
summary: collector.summary(),
|
|
633
634
|
sessionId: printSessionId,
|
|
635
|
+
compaction: agent.getCompactionStats(),
|
|
634
636
|
}) + "\n");
|
|
635
637
|
process.exitCode = 1;
|
|
636
638
|
}
|
|
@@ -4,7 +4,7 @@ import { EvidenceTracker } from "../agent/evidence-tracker.js";
|
|
|
4
4
|
import { ExecutionGovernor } from "../agent/execution-governor.js";
|
|
5
5
|
import { DiscoveryBarrier } from "../agent/discovery-barrier.js";
|
|
6
6
|
import { arbitrateToolCall } from "../agent/tool-arbiter.js";
|
|
7
|
-
import { buildEditRetryEscalationReminder, buildSmallTaskHint, buildWorkflowPhaseReminder, } from "../prompt/reminders.js";
|
|
7
|
+
import { buildCompletionSelfCheckReminder, buildEditRetryEscalationReminder, buildSmallTaskHint, buildWorkflowPhaseReminder, } from "../prompt/reminders.js";
|
|
8
8
|
import { largeImplementationTaskReminder, orchestrationRequestReminder, reminderForTaskType, userNamedModelReminder } from "../prompt/task-reminders.js";
|
|
9
9
|
import { formatCoverageSummary, resolveWorkflowPhase } from "./workflow.js";
|
|
10
10
|
import { resolve as resolvePath } from "node:path";
|
|
@@ -220,9 +220,33 @@ export function createDefaultHooks() {
|
|
|
220
220
|
}
|
|
221
221
|
// Verification reminders intentionally removed. See afterToolCall.
|
|
222
222
|
},
|
|
223
|
-
afterTurn() {
|
|
223
|
+
afterTurn(ctx) {
|
|
224
224
|
// Verification force-continuation removed. The model decides whether
|
|
225
225
|
// verification is meaningful for the task, per the system prompt.
|
|
226
|
+
//
|
|
227
|
+
// Completion self-check gate: when a run that changed code is about
|
|
228
|
+
// to end (this hook only runs on final, no-tool-call turns), remind
|
|
229
|
+
// the model ONCE to re-read the original request before finishing.
|
|
230
|
+
// Unlike the removed verification gate it never repeats — the model
|
|
231
|
+
// is explicitly told a confirming final answer ends the run — so the
|
|
232
|
+
// "prove it" spiral that killed the old design cannot start.
|
|
233
|
+
if (ctx.state.completionGateFired)
|
|
234
|
+
return;
|
|
235
|
+
if (!ctx.state.codeChanged)
|
|
236
|
+
return;
|
|
237
|
+
// Tools are frozen: the model could not act on anything the check
|
|
238
|
+
// surfaces, so another turn is pure token burn.
|
|
239
|
+
if (ctx.state.forceTextOnlyReason)
|
|
240
|
+
return;
|
|
241
|
+
// Subagents hand off to a parent that does its own closing pass, and
|
|
242
|
+
// worktree profiles explicitly forbid the very actions a completion
|
|
243
|
+
// check would suggest.
|
|
244
|
+
if (ctx.agent.role === "subagent")
|
|
245
|
+
return;
|
|
246
|
+
ctx.state.completionGateFired = true;
|
|
247
|
+
ctx.state.forceContinuationReason = "completion_self_check";
|
|
248
|
+
ctx.queueReminder(buildCompletionSelfCheckReminder());
|
|
249
|
+
traceEvent("completion_gate_fired", { turnCount: ctx.state.turnCount });
|
|
226
250
|
},
|
|
227
251
|
},
|
|
228
252
|
];
|
|
@@ -16,6 +16,8 @@ export interface TurnHookState {
|
|
|
16
16
|
forceTextOnlyReason?: string;
|
|
17
17
|
forceContinuationReason?: string;
|
|
18
18
|
codeChanged?: boolean;
|
|
19
|
+
/** Completion self-check gate already fired this run (fires at most once). */
|
|
20
|
+
completionGateFired?: boolean;
|
|
19
21
|
smallTaskHintSent?: boolean;
|
|
20
22
|
recentEditFailures?: string[];
|
|
21
23
|
editNoMatchReminderPaths?: string[];
|
package/dist/print-output.d.ts
CHANGED
|
@@ -42,13 +42,26 @@ export declare class PrintRunCollector {
|
|
|
42
42
|
private addUsage;
|
|
43
43
|
summary(): PrintRunSummary;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Per-path counts of context compactions during the run. Lets benchmark
|
|
47
|
+
* harnesses correlate outcomes with "did the model ever lose sight of the
|
|
48
|
+
* full history" without access to the in-container session files.
|
|
49
|
+
*/
|
|
50
|
+
export interface PrintCompactionStats {
|
|
51
|
+
resident: number;
|
|
52
|
+
llm: number;
|
|
53
|
+
overflow: number;
|
|
54
|
+
droppedMessages: number;
|
|
55
|
+
}
|
|
45
56
|
export declare function formatPrintJson(input: {
|
|
46
57
|
summary: PrintRunSummary;
|
|
47
58
|
sessionId?: string;
|
|
48
59
|
stopReason?: "end_turn" | "error" | "cancelled";
|
|
60
|
+
compaction?: PrintCompactionStats;
|
|
49
61
|
}): string;
|
|
50
62
|
export declare function formatPrintJsonError(input: {
|
|
51
63
|
message: string;
|
|
52
64
|
summary?: PrintRunSummary;
|
|
53
65
|
sessionId?: string;
|
|
66
|
+
compaction?: PrintCompactionStats;
|
|
54
67
|
}): string;
|
package/dist/print-output.js
CHANGED
|
@@ -83,6 +83,7 @@ export function formatPrintJson(input) {
|
|
|
83
83
|
num_tool_calls: input.summary.num_tool_calls,
|
|
84
84
|
usage: input.summary.usage,
|
|
85
85
|
usage_reported: input.summary.usage_reported,
|
|
86
|
+
...(input.compaction ? { compaction: input.compaction } : {}),
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
export function formatPrintJsonError(input) {
|
|
@@ -99,5 +100,6 @@ export function formatPrintJsonError(input) {
|
|
|
99
100
|
usage: input.summary.usage,
|
|
100
101
|
}
|
|
101
102
|
: {}),
|
|
103
|
+
...(input.compaction ? { compaction: input.compaction } : {}),
|
|
102
104
|
});
|
|
103
105
|
}
|
|
@@ -23,6 +23,7 @@ export declare function buildInvestigationReminder(): string;
|
|
|
23
23
|
export declare function buildLoopWarningReminder(reason: string): string;
|
|
24
24
|
export declare function buildSearchFreezeReminder(reason: string): string;
|
|
25
25
|
export declare function buildExplorationFreezeReminder(reason: string): string;
|
|
26
|
+
export declare function buildCompletionSelfCheckReminder(): string;
|
|
26
27
|
export declare function buildToolFreezeReminder(reason: string): string;
|
|
27
28
|
export declare function buildWorkflowPhaseReminder(input: {
|
|
28
29
|
phase: "investigate" | "correlate" | "conclude";
|
package/dist/prompt/reminders.js
CHANGED
|
@@ -119,6 +119,13 @@ Choose one of:
|
|
|
119
119
|
3. If files were already changed, run the narrowest meaningful verification or finish with the result.
|
|
120
120
|
`);
|
|
121
121
|
}
|
|
122
|
+
export function buildCompletionSelfCheckReminder() {
|
|
123
|
+
return wrapInSystemReminder(`
|
|
124
|
+
You appear to be finishing. Before giving your final answer, re-read the user's original request in this conversation and check whether any explicit requirement in it remains unfulfilled — including requirements about how the work should be delivered or reported, not only whether the implementation works.
|
|
125
|
+
|
|
126
|
+
If something is missing, complete it now. If everything is done, give your final answer directly — do not repeat this check or re-verify work you have already verified.
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
122
129
|
export function buildToolFreezeReminder(reason) {
|
|
123
130
|
return wrapInSystemReminder(`
|
|
124
131
|
CRITICAL - MAXIMUM STEPS REACHED
|
package/dist/prompt/runtime.js
CHANGED
|
@@ -11,6 +11,7 @@ const defaultGuidelines = [
|
|
|
11
11
|
"Prefer structured tools (glob, grep, read) over bash for search and inspection. Do not repeat a near-identical search or re-read the same file unless new evidence changes the question.",
|
|
12
12
|
"If a tool fails, diagnose the error before switching tactics. Do not retry the identical call with identical arguments. After two equivalent failures, switch approach — re-read the file, use a different tool, rewrite the whole file with write, or ask the user.",
|
|
13
13
|
"Before reporting a task complete, verify it works when verification is meaningful and cheap — run the existing test, execute the script, check the output. If no test exists, the change is purely declarative (static HTML/markdown/config), or running the code is not practical, state that explicitly rather than inventing a verification step. Do not write throwaway validation scripts to prove correctness; if there is no real check to run, report the change and stop.",
|
|
14
|
+
"Before declaring a task complete, re-read the user's original request and verify that every explicit requirement in it — about both the result and the process — has been fulfilled, not only that the implementation works.",
|
|
14
15
|
];
|
|
15
16
|
export function buildRuntimePrompt(options = {}) {
|
|
16
17
|
const guidelines = dedupe(defaultGuidelines, options.guidelines ?? []);
|