feat(reviewer): add Tier 1 reviewer agent role with auto-channel notifications
Reviewer workflow in agent-prompts.ts (8-step process), Rust auto-post to #review-queue on task->review transition, reviewQueueDepth in attention scoring (10pts/task cap 50), Tasks tab for reviewer in ProjectBox with 10s queue polling. 7 vitest + 4 cargo tests.
This commit is contained in:
parent
61f01e22b8
commit
323bb1b040
9 changed files with 397 additions and 0 deletions
|
|
@ -117,20 +117,110 @@ pub fn task_comments(task_id: &str) -> Result<Vec<TaskComment>, String> {
|
|||
}
|
||||
|
||||
/// Update task status
|
||||
/// When transitioning to 'review', auto-posts to #review-queue channel if it exists
|
||||
pub fn update_task_status(task_id: &str, status: &str) -> Result<(), String> {
|
||||
let valid = ["todo", "progress", "review", "done", "blocked"];
|
||||
if !valid.contains(&status) {
|
||||
return Err(format!("Invalid status '{}'. Valid: {:?}", status, valid));
|
||||
}
|
||||
let db = open_db()?;
|
||||
|
||||
// Fetch task info before update (for channel notification)
|
||||
let task_title: Option<(String, String)> = if status == "review" {
|
||||
db.query_row(
|
||||
"SELECT title, group_id FROM tasks WHERE id = ?1",
|
||||
params![task_id],
|
||||
|row| Ok((row.get::<_, String>("title")?, row.get::<_, String>("group_id")?)),
|
||||
).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
db.execute(
|
||||
"UPDATE tasks SET status = ?1, updated_at = datetime('now') WHERE id = ?2",
|
||||
params![status, task_id],
|
||||
)
|
||||
.map_err(|e| format!("Update error: {e}"))?;
|
||||
|
||||
// Auto-post to #review-queue channel on review transition
|
||||
if let Some((title, group_id)) = task_title {
|
||||
notify_review_channel(&db, &group_id, task_id, &title);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Post a notification to #review-queue channel (best-effort, never fails the parent operation)
|
||||
fn notify_review_channel(db: &Connection, group_id: &str, task_id: &str, title: &str) {
|
||||
// Find #review-queue channel for this group
|
||||
let channel_id: Option<String> = db
|
||||
.query_row(
|
||||
"SELECT id FROM channels WHERE name = 'review-queue' AND group_id = ?1",
|
||||
params![group_id],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.ok();
|
||||
|
||||
let channel_id = match channel_id {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
// Auto-create #review-queue channel
|
||||
match ensure_review_channels(db, group_id) {
|
||||
Some(id) => id,
|
||||
None => return, // Give up silently
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let msg_id = uuid::Uuid::new_v4().to_string();
|
||||
let content = format!("📋 Task ready for review: **{}** (`{}`)", title, task_id);
|
||||
let _ = db.execute(
|
||||
"INSERT INTO channel_messages (id, channel_id, from_agent, content) VALUES (?1, ?2, 'system', ?3)",
|
||||
params![msg_id, channel_id, content],
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure #review-queue and #review-log channels exist for a group.
|
||||
/// Returns the review-queue channel ID if created/found.
|
||||
fn ensure_review_channels(db: &Connection, group_id: &str) -> Option<String> {
|
||||
// Create channels only if they don't already exist
|
||||
for name in &["review-queue", "review-log"] {
|
||||
let exists: bool = db
|
||||
.query_row(
|
||||
"SELECT COUNT(*) > 0 FROM channels WHERE name = ?1 AND group_id = ?2",
|
||||
params![name, group_id],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap_or(false);
|
||||
if !exists {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
let _ = db.execute(
|
||||
"INSERT INTO channels (id, name, group_id, created_by) VALUES (?1, ?2, ?3, 'system')",
|
||||
params![id, name, group_id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the review-queue channel ID
|
||||
db.query_row(
|
||||
"SELECT id FROM channels WHERE name = 'review-queue' AND group_id = ?1",
|
||||
params![group_id],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Count tasks in 'review' status for a group
|
||||
pub fn review_queue_count(group_id: &str) -> Result<i64, String> {
|
||||
let db = open_db()?;
|
||||
db.query_row(
|
||||
"SELECT COUNT(*) FROM tasks WHERE group_id = ?1 AND status = 'review'",
|
||||
params![group_id],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map_err(|e| format!("Query error: {e}"))
|
||||
}
|
||||
|
||||
/// Add a comment to a task
|
||||
pub fn add_comment(task_id: &str, agent_id: &str, content: &str) -> Result<String, String> {
|
||||
let db = open_db()?;
|
||||
|
|
@ -201,6 +291,20 @@ mod tests {
|
|||
agent_id TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE channels (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
group_id TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE channel_messages (
|
||||
id TEXT PRIMARY KEY,
|
||||
channel_id TEXT NOT NULL,
|
||||
from_agent TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);",
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -362,4 +466,132 @@ mod tests {
|
|||
assert!(!valid.contains(&"invalid"));
|
||||
assert!(!valid.contains(&"cancelled"));
|
||||
}
|
||||
|
||||
// ---- Review channel auto-creation ----
|
||||
|
||||
#[test]
|
||||
fn test_ensure_review_channels_creates_both() {
|
||||
let conn = test_db();
|
||||
let result = ensure_review_channels(&conn, "g1");
|
||||
assert!(result.is_some(), "should return review-queue channel ID");
|
||||
|
||||
// Verify both channels exist
|
||||
let queue_count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM channels WHERE name = 'review-queue' AND group_id = 'g1'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(queue_count, 1);
|
||||
|
||||
let log_count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM channels WHERE name = 'review-log' AND group_id = 'g1'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(log_count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ensure_review_channels_idempotent() {
|
||||
let conn = test_db();
|
||||
let id1 = ensure_review_channels(&conn, "g1").unwrap();
|
||||
let id2 = ensure_review_channels(&conn, "g1").unwrap();
|
||||
assert_eq!(id1, id2, "should return same channel ID on repeated calls");
|
||||
|
||||
// Verify no duplicates
|
||||
let count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM channels WHERE name = 'review-queue' AND group_id = 'g1'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notify_review_channel_posts_message() {
|
||||
let conn = test_db();
|
||||
// Insert a task
|
||||
conn.execute(
|
||||
"INSERT INTO tasks (id, title, created_by, group_id) VALUES ('t1', 'Fix login bug', 'admin', 'g1')",
|
||||
[],
|
||||
).unwrap();
|
||||
|
||||
// Trigger notification (should auto-create channel)
|
||||
notify_review_channel(&conn, "g1", "t1", "Fix login bug");
|
||||
|
||||
// Verify message was posted
|
||||
let msg_count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM channel_messages cm
|
||||
JOIN channels c ON cm.channel_id = c.id
|
||||
WHERE c.name = 'review-queue' AND c.group_id = 'g1'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(msg_count, 1);
|
||||
|
||||
// Verify message content
|
||||
let content: String = conn
|
||||
.query_row(
|
||||
"SELECT cm.content FROM channel_messages cm
|
||||
JOIN channels c ON cm.channel_id = c.id
|
||||
WHERE c.name = 'review-queue'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(content.contains("Fix login bug"));
|
||||
assert!(content.contains("t1"));
|
||||
}
|
||||
|
||||
// ---- Review queue count ----
|
||||
|
||||
#[test]
|
||||
fn test_review_queue_count_via_sql() {
|
||||
let conn = test_db();
|
||||
// Insert tasks with various statuses
|
||||
conn.execute(
|
||||
"INSERT INTO tasks (id, title, status, created_by, group_id) VALUES ('t1', 'A', 'review', 'admin', 'g1')",
|
||||
[],
|
||||
).unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO tasks (id, title, status, created_by, group_id) VALUES ('t2', 'B', 'review', 'admin', 'g1')",
|
||||
[],
|
||||
).unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO tasks (id, title, status, created_by, group_id) VALUES ('t3', 'C', 'progress', 'admin', 'g1')",
|
||||
[],
|
||||
).unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO tasks (id, title, status, created_by, group_id) VALUES ('t4', 'D', 'review', 'admin', 'g2')",
|
||||
[],
|
||||
).unwrap();
|
||||
|
||||
// Count review tasks for g1
|
||||
let count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM tasks WHERE group_id = ?1 AND status = 'review'",
|
||||
params!["g1"],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(count, 2, "should count only review tasks in g1");
|
||||
|
||||
// Count review tasks for g2
|
||||
let count_g2: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM tasks WHERE group_id = ?1 AND status = 'review'",
|
||||
params!["g2"],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(count_g2, 1, "should count only review tasks in g2");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,3 +36,8 @@ pub fn bttask_create(
|
|||
pub fn bttask_delete(task_id: String) -> Result<(), String> {
|
||||
bttask::delete_task(&task_id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn bttask_review_queue_count(group_id: String) -> Result<i64, String> {
|
||||
bttask::review_queue_count(&group_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ pub fn run() {
|
|||
commands::bttask::bttask_add_comment,
|
||||
commands::bttask::bttask_create,
|
||||
commands::bttask::bttask_delete,
|
||||
commands::bttask::bttask_review_queue_count,
|
||||
// Misc
|
||||
commands::misc::cli_get_group,
|
||||
commands::misc::open_url,
|
||||
|
|
|
|||
|
|
@ -56,3 +56,8 @@ export async function createTask(
|
|||
export async function deleteTask(taskId: string): Promise<void> {
|
||||
return invoke('bttask_delete', { taskId });
|
||||
}
|
||||
|
||||
/** Count tasks currently in 'review' status for a group */
|
||||
export async function reviewQueueCount(groupId: GroupId): Promise<number> {
|
||||
return invoke<number>('bttask_review_queue_count', { groupId });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
import { ProjectId, type AgentId, type GroupId } from '../../types/ids';
|
||||
import { notify, dismissNotification } from '../../stores/notifications.svelte';
|
||||
import { registerManager, unregisterManager, updateManagerConfig } from '../../stores/wake-scheduler.svelte';
|
||||
import { setReviewQueueDepth } from '../../stores/health.svelte';
|
||||
import { reviewQueueCount } from '../../adapters/bttask-bridge';
|
||||
|
||||
interface Props {
|
||||
project: ProjectConfig;
|
||||
|
|
@ -93,6 +95,23 @@
|
|||
};
|
||||
});
|
||||
|
||||
// Poll review queue depth for reviewer agents (feeds into attention scoring)
|
||||
$effect(() => {
|
||||
if (!(project.isAgent && project.agentRole === 'reviewer')) return;
|
||||
const groupId = activeGroup?.id;
|
||||
if (!groupId) return;
|
||||
|
||||
const pollReviewQueue = () => {
|
||||
reviewQueueCount(groupId)
|
||||
.then(count => setReviewQueueDepth(project.id, count))
|
||||
.catch(() => {}); // best-effort
|
||||
};
|
||||
|
||||
pollReviewQueue(); // immediate first poll
|
||||
const timer = setInterval(pollReviewQueue, 10_000); // 10s poll
|
||||
return () => clearInterval(timer);
|
||||
});
|
||||
|
||||
// S-1 Phase 2: start filesystem watcher for this project's CWD
|
||||
$effect(() => {
|
||||
const cwd = project.cwd;
|
||||
|
|
@ -195,6 +214,9 @@
|
|||
{#if isAgent && agentRole === 'architect'}
|
||||
<button class="ptab ptab-role" class:active={activeTab === 'architecture'} onclick={() => switchTab('architecture')}>Arch</button>
|
||||
{/if}
|
||||
{#if isAgent && agentRole === 'reviewer'}
|
||||
<button class="ptab ptab-role" class:active={activeTab === 'tasks'} onclick={() => switchTab('tasks')}>Tasks</button>
|
||||
{/if}
|
||||
{#if isAgent && agentRole === 'tester'}
|
||||
<button class="ptab ptab-role" class:active={activeTab === 'selenium'} onclick={() => switchTab('selenium')}>Selenium</button>
|
||||
<button class="ptab ptab-role" class:active={activeTab === 'tests'} onclick={() => switchTab('tests')}>Tests</button>
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ interface ProjectTracker {
|
|||
tokenSnapshots: Array<[number, number]>;
|
||||
/** Cost snapshots for $/hr: [timestamp, costUsd] */
|
||||
costSnapshots: Array<[number, number]>;
|
||||
/** Number of tasks in 'review' status (for reviewer agents) */
|
||||
reviewQueueDepth: number;
|
||||
}
|
||||
|
||||
let trackers = $state<Map<ProjectIdType, ProjectTracker>>(new Map());
|
||||
|
|
@ -90,6 +92,7 @@ export function trackProject(projectId: ProjectIdType, sessionId: SessionIdType
|
|||
toolInFlight: false,
|
||||
tokenSnapshots: [],
|
||||
costSnapshots: [],
|
||||
reviewQueueDepth: 0,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -179,6 +182,12 @@ export function stopHealthTick(): void {
|
|||
}
|
||||
}
|
||||
|
||||
/** Set review queue depth for a project (used by reviewer agents) */
|
||||
export function setReviewQueueDepth(projectId: ProjectIdType, depth: number): void {
|
||||
const t = trackers.get(projectId);
|
||||
if (t) t.reviewQueueDepth = depth;
|
||||
}
|
||||
|
||||
/** Clear all tracked projects */
|
||||
export function clearHealthTracking(): void {
|
||||
trackers = new Map();
|
||||
|
|
@ -255,6 +264,7 @@ function computeHealth(tracker: ProjectTracker, now: number): ProjectHealth {
|
|||
contextPressure,
|
||||
fileConflictCount,
|
||||
externalConflictCount,
|
||||
reviewQueueDepth: tracker.reviewQueueDepth,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,29 @@ ${roleDesc}
|
|||
parts.push(BTMSG_DOCS);
|
||||
if (role === 'manager' || role === 'architect') {
|
||||
parts.push(BTTASK_DOCS);
|
||||
} else if (role === 'reviewer') {
|
||||
// Reviewer gets full read + status update + comment access
|
||||
parts.push(`
|
||||
## Tool: bttask — Task Board (review access)
|
||||
|
||||
You have full read access plus the ability to update task status and add comments.
|
||||
You CANNOT create, assign, or delete tasks (Manager only).
|
||||
|
||||
\`\`\`bash
|
||||
bttask board # Kanban board view
|
||||
bttask show <task-id> # Full task details + comments
|
||||
bttask list # List all tasks
|
||||
bttask status <task-id> done # Approve — mark as done
|
||||
bttask status <task-id> progress # Request changes — send back
|
||||
bttask status <task-id> blocked # Block — explain in comment!
|
||||
bttask comment <task-id> "verdict" # Add review verdict/feedback
|
||||
\`\`\`
|
||||
|
||||
### Review workflow with bttask
|
||||
- Tasks in the **review** column are waiting for YOUR review
|
||||
- After reviewing, either move to **done** (approved) or **progress** (needs changes)
|
||||
- ALWAYS add a comment with your verdict before changing status
|
||||
- When a task moves to review, a notification is auto-posted to \`#review-queue\``);
|
||||
} else {
|
||||
// Other agents get read-only bttask info
|
||||
parts.push(`
|
||||
|
|
@ -329,6 +352,29 @@ If the Operator sends a message, it's your TOP PRIORITY.`;
|
|||
6. **Verify fixes:** Re-test when developers say a bug is fixed`;
|
||||
}
|
||||
|
||||
if (role === 'reviewer') {
|
||||
return `## Your Workflow
|
||||
|
||||
1. **Check inbox:** \`btmsg inbox\` — read review requests and messages
|
||||
2. **Check review queue:** \`btmsg channel history review-queue\` — see newly submitted reviews
|
||||
3. **Review tasks:** \`bttask board\` — find tasks in the **review** column
|
||||
4. **Analyze:** For each review task:
|
||||
a. Read the task description and comments (\`bttask show <id>\`)
|
||||
b. Read the relevant code changes
|
||||
c. Check for security issues, bugs, style violations, and test coverage
|
||||
5. **Verdict:** Add your review as a comment (\`bttask comment <id> "APPROVED: ..."\` or \`"CHANGES REQUESTED: ..."\`)
|
||||
6. **Update status:** Move task to **done** (approved) or **progress** (needs changes)
|
||||
7. **Log verdict:** Post summary to \`btmsg channel send review-log "Task <id>: APPROVED/REJECTED — reason"\`
|
||||
8. **Report:** Notify the Manager of review outcomes if significant
|
||||
|
||||
**Review standards:**
|
||||
- Code quality: readability, naming, structure
|
||||
- Security: input validation, auth checks, injection risks
|
||||
- Error handling: all errors caught and handled visibly
|
||||
- Tests: adequate coverage for new/changed code
|
||||
- Performance: no N+1 queries, unbounded fetches, or memory leaks`;
|
||||
}
|
||||
|
||||
return `## Your Workflow
|
||||
|
||||
1. **Check inbox:** \`btmsg inbox\` — read all unread messages
|
||||
|
|
|
|||
|
|
@ -136,4 +136,66 @@ describe('scoreAttention', () => {
|
|||
}));
|
||||
expect(result.reason).toContain('Unknown');
|
||||
});
|
||||
|
||||
// --- Review queue depth scoring ---
|
||||
|
||||
it('scores review queue depth at 10 per task', () => {
|
||||
const result = scoreAttention(makeInput({
|
||||
activityState: 'running',
|
||||
reviewQueueDepth: 3,
|
||||
}));
|
||||
expect(result.score).toBe(30);
|
||||
expect(result.reason).toContain('3 tasks awaiting review');
|
||||
});
|
||||
|
||||
it('caps review queue score at 50', () => {
|
||||
const result = scoreAttention(makeInput({
|
||||
activityState: 'running',
|
||||
reviewQueueDepth: 8,
|
||||
}));
|
||||
expect(result.score).toBe(50);
|
||||
expect(result.reason).toContain('8 tasks');
|
||||
});
|
||||
|
||||
it('uses singular grammar for 1 review task', () => {
|
||||
const result = scoreAttention(makeInput({
|
||||
activityState: 'running',
|
||||
reviewQueueDepth: 1,
|
||||
}));
|
||||
expect(result.score).toBe(10);
|
||||
expect(result.reason).toBe('1 task awaiting review');
|
||||
});
|
||||
|
||||
it('review queue has lower priority than file conflicts', () => {
|
||||
const result = scoreAttention(makeInput({
|
||||
activityState: 'running',
|
||||
fileConflictCount: 2,
|
||||
reviewQueueDepth: 5,
|
||||
}));
|
||||
expect(result.score).toBe(70); // file conflicts win
|
||||
});
|
||||
|
||||
it('review queue has higher priority than context high', () => {
|
||||
const result = scoreAttention(makeInput({
|
||||
activityState: 'running',
|
||||
contextPressure: 0.80,
|
||||
reviewQueueDepth: 2,
|
||||
}));
|
||||
expect(result.score).toBe(20); // review queue wins over context high (40)
|
||||
});
|
||||
|
||||
it('ignores review queue when depth is 0', () => {
|
||||
const result = scoreAttention(makeInput({
|
||||
activityState: 'running',
|
||||
reviewQueueDepth: 0,
|
||||
}));
|
||||
expect(result.score).toBe(0);
|
||||
});
|
||||
|
||||
it('ignores review queue when undefined', () => {
|
||||
const result = scoreAttention(makeInput({
|
||||
activityState: 'running',
|
||||
}));
|
||||
expect(result.score).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ const SCORE_CONTEXT_CRITICAL = 80; // >90% context
|
|||
const SCORE_FILE_CONFLICT = 70;
|
||||
const SCORE_CONTEXT_HIGH = 40; // >75% context
|
||||
|
||||
// Review queue scoring: 10pts per stale review, capped at 50
|
||||
const SCORE_REVIEW_PER_TASK = 10;
|
||||
const SCORE_REVIEW_CAP = 50;
|
||||
|
||||
export interface AttentionInput {
|
||||
sessionStatus: string | undefined;
|
||||
sessionError: string | undefined;
|
||||
|
|
@ -18,6 +22,8 @@ export interface AttentionInput {
|
|||
contextPressure: number | null;
|
||||
fileConflictCount: number;
|
||||
externalConflictCount: number;
|
||||
/** Number of tasks in 'review' status (for reviewer agents) */
|
||||
reviewQueueDepth?: number;
|
||||
}
|
||||
|
||||
export interface AttentionResult {
|
||||
|
|
@ -57,6 +63,14 @@ export function scoreAttention(input: AttentionInput): AttentionResult {
|
|||
};
|
||||
}
|
||||
|
||||
if (input.reviewQueueDepth && input.reviewQueueDepth > 0) {
|
||||
const score = Math.min(input.reviewQueueDepth * SCORE_REVIEW_PER_TASK, SCORE_REVIEW_CAP);
|
||||
return {
|
||||
score,
|
||||
reason: `${input.reviewQueueDepth} task${input.reviewQueueDepth > 1 ? 's' : ''} awaiting review`,
|
||||
};
|
||||
}
|
||||
|
||||
if (input.contextPressure !== null && input.contextPressure > 0.75) {
|
||||
return {
|
||||
score: SCORE_CONTEXT_HIGH,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue