mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
feat: smart title extraction for subagent descriptions
- Condenses long prompts to ~45 chars while preserving meaning - Removes filler words (the, a, please, I need you to, etc.) - Cuts at natural boundaries (colons, dashes, commas) - Falls back to word-boundary truncation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
|
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
|
||||||
|
|
||||||
**Version**: 0.1355
|
**Version**: 0.1356
|
||||||
|
|
||||||
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1355",
|
"version": "0.1356",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
+59
-18
@@ -404,6 +404,64 @@ export class SubagentWatcher extends EventEmitter {
|
|||||||
return workingDir.replace(/\//g, '-');
|
return workingDir.replace(/\//g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a smart, concise title from a task prompt
|
||||||
|
* Aims for ~40-50 chars that convey what the agent is doing
|
||||||
|
*/
|
||||||
|
private extractSmartTitle(text: string): string {
|
||||||
|
const MAX_LEN = 45;
|
||||||
|
|
||||||
|
// Get first line/sentence
|
||||||
|
let title = text.split('\n')[0].trim();
|
||||||
|
|
||||||
|
// If already short enough, use it
|
||||||
|
if (title.length <= MAX_LEN) {
|
||||||
|
return title.replace(/[.!?,\s]+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove common filler phrases to condense
|
||||||
|
const fillers = [
|
||||||
|
/^(please |i need you to |i want you to |can you |could you )/i,
|
||||||
|
/ (the|a|an) /gi,
|
||||||
|
/ (in|at|on|to|for|of|with|from|by) the /gi,
|
||||||
|
/ (including|related to|regarding|about) /gi,
|
||||||
|
/[""]/g,
|
||||||
|
/ +/g, // multiple spaces to single
|
||||||
|
];
|
||||||
|
|
||||||
|
let condensed = title;
|
||||||
|
for (const filler of fillers) {
|
||||||
|
condensed = condensed.replace(filler, (match) => {
|
||||||
|
// Keep single space for word boundaries
|
||||||
|
if (match.trim() === '') return ' ';
|
||||||
|
if (/^(the|a|an)$/i.test(match.trim())) return ' ';
|
||||||
|
if (/including|related to|regarding|about/i.test(match)) return ': ';
|
||||||
|
return ' ';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
condensed = condensed.replace(/ +/g, ' ').trim();
|
||||||
|
|
||||||
|
// If condensed version is short enough, use it
|
||||||
|
if (condensed.length <= MAX_LEN) {
|
||||||
|
return condensed.replace(/[.!?,:\s]+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to cut at a natural boundary (colon, dash, comma)
|
||||||
|
const boundaryMatch = condensed.substring(0, MAX_LEN + 5).match(/^(.{20,}?)[:\-,]/);
|
||||||
|
if (boundaryMatch && boundaryMatch[1].length <= MAX_LEN) {
|
||||||
|
return boundaryMatch[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: truncate at word boundary
|
||||||
|
const truncated = condensed.substring(0, MAX_LEN);
|
||||||
|
const lastSpace = truncated.lastIndexOf(' ');
|
||||||
|
if (lastSpace > 20) {
|
||||||
|
return truncated.substring(0, lastSpace).replace(/[.!?,:\s]+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return truncated.replace(/[.!?,:\s]+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan for all subagent directories
|
* Scan for all subagent directories
|
||||||
*/
|
*/
|
||||||
@@ -518,24 +576,7 @@ export class SubagentWatcher extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
// Extract a useful title: first line, first sentence, or first 80 chars
|
description = this.extractSmartTitle(text);
|
||||||
// Split on newline first
|
|
||||||
const firstLine = text.split('\n')[0].trim();
|
|
||||||
// If still long, try to find a sentence boundary
|
|
||||||
let title = firstLine;
|
|
||||||
if (title.length > 80) {
|
|
||||||
// Look for sentence end (. ! ?) within first 100 chars
|
|
||||||
const sentenceEnd = title.substring(0, 100).search(/[.!?]/);
|
|
||||||
if (sentenceEnd > 10) {
|
|
||||||
title = title.substring(0, sentenceEnd + 1);
|
|
||||||
} else {
|
|
||||||
// Truncate at word boundary
|
|
||||||
const truncated = title.substring(0, 80);
|
|
||||||
const lastSpace = truncated.lastIndexOf(' ');
|
|
||||||
title = lastSpace > 40 ? truncated.substring(0, lastSpace) + '...' : truncated + '...';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
description = title;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user