Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions scripts/loc-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ ALLOWLIST=(
# debt, not something a security patch should take on splitting. Frozen
# at <= FROZEN_LIMIT like the others; a real split is separate work.
src/github/bridge/tick.rs
# Already 1554 lines on master before item #87's comment-thread-cap fix
# touched it -- same situation as tick.rs above: pre-existing debt a
# small, unrelated fix shouldn't be blocked on splitting. Frozen at
# <= FROZEN_LIMIT; a real split is separate work.
src/cli/work.rs
)

cd "$(dirname "$0")/.."
Expand Down
67 changes: 65 additions & 2 deletions src/cli/work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ pub struct WorkArgs {
/// success-comment reply doing on the way out.
const HANDOFF_ASSET_MAX_CHARS: usize = 8_000;

/// Cap on how much of the concatenated "Prior discussion" thread gets
/// inlined into the dispatch prompt. #81 bounded a single outgoing reply
/// comment (`cap_reply_for_comment`); this applies the same tail-and-pointer
/// discipline to the *incoming* thread of already-bounded comments, which
/// had no aggregate cap of its own -- and since #441 moved prompt delivery
/// from argv to stdin, there's no OS-level length limit left to catch it
/// either.
const COMMENTS_PROMPT_MAX_CHARS: usize = 8_000;

/// The last `max_chars` characters of `s`, UTF-8-boundary-safe.
fn tail_chars(s: &str, max_chars: usize) -> &str {
match s.char_indices().rev().nth(max_chars.saturating_sub(1)) {
Expand Down Expand Up @@ -175,9 +184,21 @@ fn build_prompt(
)
};
if !comments.is_empty() {
prompt.push_str("\nPrior discussion:\n");
let mut discussion = String::new();
for c in comments {
prompt.push_str(&format!("- [{}] {}\n", c.author_agent, c.body));
discussion.push_str(&format!("- [{}] {}\n", c.author_agent, c.body));
}
prompt.push_str("\nPrior discussion:\n");
let total_chars = discussion.chars().count();
if total_chars <= COMMENTS_PROMPT_MAX_CHARS {
prompt.push_str(&discussion);
} else {
prompt.push_str(&format!(
"(showing the last {COMMENTS_PROMPT_MAX_CHARS} of {total_chars} chars -- full \
thread via `mcp__flare__comment` action=list item_id={})\n\n",
item.id
));
prompt.push_str(tail_chars(&discussion, COMMENTS_PROMPT_MAX_CHARS));
}
}
if let Some(handoff) = latest_handoff {
Expand Down Expand Up @@ -944,6 +965,48 @@ mod tests {
assert!(!prompt.contains("Prior discussion"));
}

#[test]
fn build_prompt_leaves_a_thread_within_budget_unchanged() {
let item = test_item();
let comments = vec![agentflare_backend::comment::ItemComment {
id: "c1".into(),
item_id: "item-1".into(),
author_agent: "alice".into(),
body: "probably a race in the setup fixture".into(),
created_at: 0,
updated_at: 0,
}];
let prompt = build_prompt(&item, &comments, None);
assert!(prompt.contains("probably a race in the setup fixture"));
assert!(!prompt.contains("showing the last"));
}

#[test]
fn build_prompt_caps_an_oversized_thread_at_the_tail_with_a_comment_pointer() {
// A thread of many individually-bounded (#81) comments still has no
// aggregate cap -- an oversized thread must be capped on the way in
// to the dispatch prompt, not dumped unbounded.
let item = test_item();
let comments = vec![agentflare_backend::comment::ItemComment {
id: "c1".into(),
item_id: "item-1".into(),
author_agent: "alice".into(),
body: "x".repeat(COMMENTS_PROMPT_MAX_CHARS * 3),
created_at: 0,
updated_at: 0,
}];
let prompt = build_prompt(&item, &comments, None);
assert!(prompt.contains("showing the last"));
assert!(prompt.contains(&format!(
"mcp__flare__comment` action=list item_id={}",
item.id
)));
// The tail of the (capped) discussion is present, but not the full
// oversized body.
assert!(prompt.contains(&"x".repeat(COMMENTS_PROMPT_MAX_CHARS - 1)));
assert!(!prompt.contains(&"x".repeat(COMMENTS_PROMPT_MAX_CHARS * 3)));
}

#[test]
fn build_prompt_instructs_the_agent_to_commit_before_finishing() {
// Item #57: a headless run that edited files but never ran `git
Expand Down
Loading