Skip to content

Commit a2e8489

Browse files
authored
Merge pull request #483 from phodal/master
update Agent rules
2 parents ce48f7c + 1beb08c commit a2e8489

File tree

16 files changed

+313
-173
lines changed

16 files changed

+313
-173
lines changed

AGENTS.md

Lines changed: 36 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,56 @@
11
## Core Rules
22

3-
- Current project is a Kotlin multiplatform project, always consider the multiplatform aspect, JS, WASM, Desktop JVM, Android, iOS
4-
- Current project is a Kotlin multiplatform project, always consider the multiplatform aspect, JS, WASM, Desktop JVM, Android, iOS
5-
- Current project is a Kotlin multiplatform project, always consider the multiplatform aspect, JS, WASM, Desktop JVM, Android, iOS
3+
- **Kotlin Multiplatform**: Always consider all platforms: JS, WASM, Desktop JVM, Android, iOS
4+
- **Build & Test**: Always run build and tests before completing tasks
5+
- **Preserve Intent**: If existing solution doesn't work, preserve its intent
6+
- **Module Clean**: Use `./gradlew :module:clean` instead of global clean
7+
- **Test Scripts**: Put temporary scripts under `docs/test-scripts`
8+
- **Logs**: Check `~/.autodev/logs/autodev-app.log` for debugging
69

7-
Rest:
10+
## KMP Best Practices
811

9-
- Always run the build and tests before completing a task, making sure they pass.
10-
- If an existing request/solution does not work, preserve its intent.
11-
- Do not run `./gradlew clean`. Clean only the specific module, e.g., `./gradlew :mpp-core:clean`.
12-
- Put temporary test scripts under `docs/test-scripts`.
12+
- Use `expect`/`actual` for platform-specific code, for example `Platform`
13+
- **@JsExport**: Use concrete classes (not interfaces), `Promise` (not `Flow`)
14+
- **WASM**: Avoid emoji and UTF-8 in code
15+
- **i18n**: Run `./gradlew :mpp-ui:generateI18n4kFiles`
1316

14-
## Debug
17+
## Renderer System
1518

16-
- Log save in `~/.autodev/logs/autodev-app.log`
19+
When modifying `CodingAgentRenderer`, update ALL implementations:
20+
- **Kotlin**: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, `JsRendererAdapter`
21+
- **TypeScript**: `BaseRenderer.ts`, `CliRenderer.ts`, `ServerRenderer.ts`, `TuiRenderer.ts`
22+
- **VSCode**: `mpp-vscode/src/bridge/mpp-core.ts`, `mpp-vscode/src/providers/chat-view.ts`
23+
- **JVM CLI**: `CodingCliRenderer`, `ConsoleRenderer`
1724

18-
## Summary
25+
## Design System
1926

20-
- Omit a summary if the problem is simple. For bug fixes, summarize as: Problem → Root Cause → Solution. Keep summary
21-
short if need. Use Mermaid for long chat only.
27+
- **CLI/TUI**: Use `semanticInk`/`semanticChalk` from `mpp-ui/src/jsMain/typescript/design-system/`
28+
- **Compose**: Use `AutoDevColors` or `MaterialTheme.colorScheme`
29+
- **NO hardcoded colors** - always use design tokens
30+
- **Docs**: `docs/design-system-color.md`, `docs/design-system-compose.md`
2231

23-
## Kotlin Multiplatform \(KMP\) Best Practices for `mpp-core` and `mpp-ui`
32+
## Quick Commands
2433

25-
- Use `expect`/`actual` for platform-specific code \(e.g., file I/O on JVM/JS/Wasm\).
26-
- Check export first, if some functions not working well with CLI (TypeScript)
27-
- 在 Kotlin/JS 的 @JsExport 中:
28-
- Avoid `Flow`, use `Promise`
29-
- ✅ 使用具体类作为返回类型和参数类型
30-
- ❌ 避免使用接口类型(JS 端无法正确处理接口的类型转换)
31-
- For WASM platform, we should not use emoji and utf8 in code.
32-
- Use ./gradlew :mpp-ui:generateI18n4kFiles for i18n
33-
34-
## Design System \(Color & Theme\)
35-
36-
- **CLI/TUI (TypeScript)**: Use `mpp-ui/src/jsMain/typescript/design-system/` → Import `semanticInk` / `semanticChalk`
37-
- **Compose (Desktop/Android)**: Use `AutoDevColors` from `cc.unitmesh.devins.ui.compose.theme` → Or `MaterialTheme.colorScheme`
38-
- **DO NOT hardcode colors** \(e.g., `Color(0xFF...)` or `#hex`\). Always use design tokens for consistency across platforms.
39-
- **Docs**: See `docs/design-system-color.md` (TypeScript) and `docs/design-system-compose.md` (Kotlin Compose)
40-
41-
## AutoDev CLI Quick Test
42-
43-
1. Build MPP Core: `cd /Volumes/source/ai/autocrud && ./gradlew :mpp-core:assembleJsPackage`
44-
2. Build and run MPP CLI: `cd mpp-ui && npm run build && npm run start`
45-
46-
## IntelliJ IDEA Plugin (mpp-idea)
34+
**CLI Test:**
35+
```bash
36+
./gradlew :mpp-core:assembleJsPackage
37+
cd mpp-ui && npm run build && npm run start
38+
```
4739

48-
`mpp-idea` is a standalone Gradle project with `includeBuild` dependency on parent project.
40+
**IDEA Plugin:**
4941

50-
**Build Commands:**
5142
```bash
52-
# Compile (from project root)
5343
cd mpp-idea && ../gradlew compileKotlin
54-
55-
# Run tests (JewelRendererTest uses JUnit 5, no IntelliJ Platform required)
5644
cd mpp-idea && ../gradlew test --tests "cc.unitmesh.devins.idea.renderer.JewelRendererTest"
57-
58-
# Build plugin
5945
cd mpp-idea && ../gradlew buildPlugin
6046
```
6147

62-
**Notes:**
63-
- Do NOT use `./gradlew :mpp-idea:compileKotlin` from root - use `cd mpp-idea && ../gradlew` instead
64-
- `IdeaAgentViewModelTest` requires IntelliJ Platform Test Framework
65-
- `JewelRendererTest` can run standalone with JUnit 5
66-
67-
## VSCode Plugin (mpp-vscode)
68-
69-
`mpp-vscode` is a standalone npm package with `devDependencies` on parent project.
70-
71-
## Release
48+
**Release:**
7249

73-
1. modify version in `gradle.properties`
74-
2. publish cli version: `cd mpp-ui && npm publish:remote`
75-
76-
### Desktop Compose App
77-
78-
1. publish Desktop: `git tag compose-vVersion` (same in `gradle.properties`), `git push origin compose-vVersion`
79-
2. draft release in GitHub, run gh cli: `gh release create compose-vVersion --draft`
50+
```bash
51+
# 1. Update version in gradle.properties
52+
# 2. CLI: cd mpp-ui && npm publish:remote
53+
# 3. Desktop: git tag compose-vX.X.X && git push origin compose-vX.X.X
54+
# 4. gh release create compose-vX.X.X --draft
55+
```
8056

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/executor/CodingAgentExecutor.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ class CodingAgentExecutor(
6262
private val recentToolCalls = mutableListOf<String>()
6363
private val MAX_REPEAT_COUNT = 3
6464

65+
// Track task execution time
66+
private var taskStartTime: Long = 0L
67+
6568
/**
6669
* 执行 Agent 任务
6770
*/
@@ -71,13 +74,17 @@ class CodingAgentExecutor(
7174
onProgress: (String) -> Unit = {}
7275
): AgentResult {
7376
resetExecution()
77+
78+
// Start tracking execution time
79+
taskStartTime = Platform.getCurrentTimestamp()
80+
7481
conversationManager = ConversationManager(llmService, systemPrompt)
75-
82+
7683
// Set up token tracking callback to update renderer
7784
conversationManager?.onTokenUpdate = { tokenInfo ->
7885
renderer.updateTokenInfo(tokenInfo)
7986
}
80-
87+
8188
val initialUserMessage = buildInitialUserMessage(task)
8289

8390
onProgress("🚀 CodingAgent started")
@@ -103,7 +110,8 @@ class CodingAgentExecutor(
103110

104111
val allToolCalls = toolCallParser.parseToolCalls(llmResponse.toString())
105112
if (allToolCalls.isEmpty()) {
106-
renderer.renderTaskComplete()
113+
val executionTimeMs = Platform.getCurrentTimestamp() - taskStartTime
114+
renderer.renderTaskComplete(executionTimeMs)
107115
break
108116
}
109117

@@ -122,7 +130,9 @@ class CodingAgentExecutor(
122130
conversationManager!!.addToolResults(toolResultsText)
123131

124132
if (isTaskComplete(llmResponse.toString())) {
125-
renderer.renderTaskComplete()
133+
val executionTimeMs = Platform.getCurrentTimestamp() - taskStartTime
134+
val toolsUsedCount = steps.size
135+
renderer.renderTaskComplete(executionTimeMs, toolsUsedCount)
126136
break
127137
}
128138

@@ -140,6 +150,7 @@ class CodingAgentExecutor(
140150
steps.clear()
141151
edits.clear()
142152
recentToolCalls.clear()
153+
taskStartTime = 0L
143154
}
144155

145156
private fun buildInitialUserMessage(task: AgentTask): String {

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/CodingAgentRenderer.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ interface CodingAgentRenderer {
3535
metadata: Map<String, String> = emptyMap()
3636
)
3737

38-
fun renderTaskComplete()
38+
/**
39+
* Render task completion message with execution time and tool usage statistics.
40+
*
41+
* @param executionTimeMs Total execution time in milliseconds from task start to completion
42+
* @param toolsUsedCount Number of tools used during execution
43+
*/
44+
fun renderTaskComplete(executionTimeMs: Long = 0L, toolsUsedCount: Int = 0)
3945
fun renderFinalResult(success: Boolean, message: String, iterations: Int)
4046
fun renderError(message: String)
4147
fun renderRepeatWarning(toolName: String, count: Int)

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/render/DefaultCodingAgentRenderer.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,21 @@ class DefaultCodingAgentRenderer : BaseRenderer() {
6868
println()
6969
}
7070

71-
override fun renderTaskComplete() {
72-
println("✓ Task marked as complete\n")
71+
override fun renderTaskComplete(executionTimeMs: Long, toolsUsedCount: Int) {
72+
val parts = mutableListOf<String>()
73+
74+
if (executionTimeMs > 0) {
75+
val seconds = executionTimeMs / 1000.0
76+
val rounded = (seconds * 100).toLong() / 100.0
77+
parts.add("${rounded}s")
78+
}
79+
80+
if (toolsUsedCount > 0) {
81+
parts.add("$toolsUsedCount tools")
82+
}
83+
84+
val info = if (parts.isNotEmpty()) " (${parts.joinToString(", ")})" else ""
85+
println("✓ Task marked as complete$info\n")
7386
}
7487

7588
override fun renderFinalResult(success: Boolean, message: String, iterations: Int) {

mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/RendererExports.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ interface JsCodingAgentRenderer {
102102
fun renderToolResult(toolName: String, success: Boolean, output: String?, fullOutput: String?)
103103

104104
// Status and completion methods
105-
fun renderTaskComplete()
105+
fun renderTaskComplete(executionTimeMs: Double = 0.0, toolsUsedCount: Int = 0)
106106
fun renderFinalResult(success: Boolean, message: String, iterations: Int)
107107
fun renderError(message: String)
108108
fun renderRepeatWarning(toolName: String, count: Int)
@@ -155,8 +155,8 @@ class JsRendererAdapter(private val jsRenderer: JsCodingAgentRenderer) : CodingA
155155
jsRenderer.renderToolResult(toolName, success, output, fullOutput)
156156
}
157157

158-
override fun renderTaskComplete() {
159-
jsRenderer.renderTaskComplete()
158+
override fun renderTaskComplete(executionTimeMs: Long, toolsUsedCount: Int) {
159+
jsRenderer.renderTaskComplete(executionTimeMs.toDouble(), toolsUsedCount)
160160
}
161161

162162
override fun renderFinalResult(success: Boolean, message: String, iterations: Int) {

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/JewelRenderer.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,33 @@ class JewelRenderer : BaseRenderer() {
407407
}
408408
}
409409

410-
override fun renderTaskComplete() {
410+
override fun renderTaskComplete(executionTimeMs: Long, toolsUsedCount: Int) {
411411
_taskCompleted.value = true
412412
_isProcessing.value = false
413+
414+
// Add a completion message with execution time and tool usage to the timeline
415+
val parts = mutableListOf<String>()
416+
417+
if (executionTimeMs > 0) {
418+
val seconds = executionTimeMs / 1000.0
419+
val rounded = (seconds * 100).toLong() / 100.0
420+
parts.add("${rounded}s")
421+
}
422+
423+
if (toolsUsedCount > 0) {
424+
parts.add("$toolsUsedCount tools")
425+
}
426+
427+
if (parts.isNotEmpty()) {
428+
_timeline.update { items ->
429+
items + TimelineItem.MessageItem(
430+
message = Message(
431+
role = MessageRole.ASSISTANT,
432+
content = "✓ Task marked as complete (${parts.joinToString(", ")})"
433+
)
434+
)
435+
}
436+
}
413437
}
414438

415439
override fun renderFinalResult(success: Boolean, message: String, iterations: Int) {

mpp-server/src/main/kotlin/cc/unitmesh/server/render/ServerSideRenderer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class ServerSideRenderer : CodingAgentRenderer {
4343
eventChannel.trySend(AgentEvent.ToolResult(toolName, success, output))
4444
}
4545

46-
override fun renderTaskComplete() {
46+
override fun renderTaskComplete(executionTimeMs: Long, toolsUsedCount: Int) {
4747
// Will be handled by renderFinalResult
4848
}
4949

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/ComposeRenderer.kt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ class ComposeRenderer : BaseRenderer() {
5555
val errorMessage: String? get() = _errorMessage
5656

5757
private var _taskCompleted by mutableStateOf(false)
58-
val taskCompleted: Boolean get() = _taskCompleted
5958

6059
private var _executionStartTime by mutableStateOf(0L)
6160
val executionStartTime: Long get() = _executionStartTime
@@ -73,7 +72,6 @@ class ComposeRenderer : BaseRenderer() {
7372
private var _currentViewingFile by mutableStateOf<String?>(null)
7473
val currentViewingFile: String? get() = _currentViewingFile
7574

76-
// Task tracking from task-boundary tool
7775
private val _tasks = mutableStateListOf<TaskInfo>()
7876
val tasks: List<TaskInfo> = _tasks
7977

@@ -465,9 +463,33 @@ class ComposeRenderer : BaseRenderer() {
465463
}
466464
}
467465

468-
override fun renderTaskComplete() {
466+
override fun renderTaskComplete(executionTimeMs: Long, toolsUsedCount: Int) {
469467
_taskCompleted = true
470468
_isProcessing = false
469+
470+
// Add a completion message with execution time and tool usage to the timeline
471+
val parts = mutableListOf<String>()
472+
473+
if (executionTimeMs > 0) {
474+
val seconds = executionTimeMs / 1000.0
475+
val rounded = (seconds * 100).toLong() / 100.0
476+
parts.add("${rounded}s")
477+
}
478+
479+
if (toolsUsedCount > 0) {
480+
parts.add("$toolsUsedCount tools")
481+
}
482+
483+
if (parts.isNotEmpty()) {
484+
_timeline.add(
485+
TimelineItem.MessageItem(
486+
message = Message(
487+
role = MessageRole.ASSISTANT,
488+
content = "✓ Task marked as complete (${parts.joinToString(", ")})"
489+
)
490+
)
491+
)
492+
}
471493
}
472494

473495
override fun renderFinalResult(

mpp-ui/src/jsMain/typescript/agents/render/BaseRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export abstract class BaseRenderer implements JsCodingAgentRenderer {
115115
abstract renderLLMResponseEnd(): void;
116116
abstract renderToolCall(toolName: string, paramsStr: string): void;
117117
abstract renderToolResult(toolName: string, success: boolean, output: string | null, fullOutput?: string | null, metadata?: Record<string, string>): void;
118-
abstract renderTaskComplete(): void;
118+
abstract renderTaskComplete(executionTimeMs?: number, toolsUsedCount?: number): void;
119119
abstract renderFinalResult(success: boolean, message: string, iterations: number): void;
120120
abstract renderError(message: string): void;
121121
abstract renderRepeatWarning(toolName: string, count: number): void;

mpp-ui/src/jsMain/typescript/agents/render/CliRenderer.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,20 @@ export class CliRenderer extends BaseRenderer {
524524
return null;
525525
}
526526

527-
renderTaskComplete(): void {
528-
console.log(semanticChalk.successBold('\n✓ Task marked as complete\n'));
527+
renderTaskComplete(executionTimeMs: number = 0, toolsUsedCount: number = 0): void {
528+
const parts: string[] = [];
529+
530+
if (executionTimeMs > 0) {
531+
const seconds = (executionTimeMs / 1000).toFixed(2);
532+
parts.push(`${seconds}s`);
533+
}
534+
535+
if (toolsUsedCount > 0) {
536+
parts.push(`${toolsUsedCount} tools`);
537+
}
538+
539+
const info = parts.length > 0 ? ` (${parts.join(', ')})` : '';
540+
console.log(semanticChalk.successBold(`\n✓ Task marked as complete${info}\n`));
529541
}
530542

531543
renderFinalResult(success: boolean, message: string, iterations: number): void {

0 commit comments

Comments
 (0)