codemini-cli 0.3.4 → 0.3.6
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/README.md +20 -18
- package/package.json +6 -6
- package/souls/anime.md +12 -9
- package/souls/caveman.md +6 -6
- package/souls/ceo.md +10 -9
- package/souls/default.md +1 -1
- package/souls/pirate.md +6 -6
- package/souls/playful.md +7 -7
- package/souls/professional.md +1 -1
- package/src/cli.js +3 -1
- package/src/commands/run.js +229 -16
- package/src/core/agent-loop.js +167 -49
- package/src/core/ast.js +40 -0
- package/src/core/chat-runtime.js +720 -126
- package/src/core/command-policy.js +56 -0
- package/src/core/config-store.js +0 -3
- package/src/core/crypto-utils.js +6 -2
- package/src/core/memory-store.js +3 -3
- package/src/core/project-index.js +4 -18
- package/src/core/provider/anthropic.js +15 -2
- package/src/core/provider/anthropic.sdk-backup.js +439 -0
- package/src/core/provider/openai-compatible.js +93 -11
- package/src/core/provider/openai-compatible.sdk-backup.js +412 -0
- package/src/core/session-store.js +90 -25
- package/src/core/shell-profile.js +26 -6
- package/src/core/string-utils.js +37 -0
- package/src/core/tools.js +216 -405
- package/src/tui/chat-app.js +490 -146
- package/src/tui/tool-activity/presenters/files.js +2 -2
- package/src/tui/tool-narration.js +0 -3
- package/src/tui/tool-narration/presenters/patch.js +0 -3
package/README.md
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
## English
|
|
7
7
|
|
|
8
|
-
CodeMini CLI is a terminal coding assistant built for teams that want a
|
|
8
|
+
CodeMini CLI is a terminal coding assistant built for teams that want a sharper, more controllable, and model-agnostic agent experience.
|
|
9
9
|
|
|
10
10
|
It is designed around a deliberate idea: most coding workflows do not need a huge default tool surface or unrestricted shell behavior. Instead, CodeMini starts with a compact core, loads advanced tools on demand, and keeps the agent grounded in structured code operations, session todos, lightweight project indexing, and shell-aware safety rules.
|
|
11
11
|
|
|
12
12
|
### Why CodeMini CLI
|
|
13
13
|
|
|
14
|
-
- Built for practical coding workflows
|
|
14
|
+
- Built for practical coding workflows across both frontier-scale and smaller/internal models
|
|
15
15
|
- Keeps the default tool list intentionally small, with additional tools discoverable through `tool_search`
|
|
16
16
|
- Treats Windows and PowerShell as first-class environments instead of Linux-only afterthoughts
|
|
17
17
|
- Prefers structured file and code tools over noisy shell fallbacks
|
|
@@ -22,7 +22,7 @@ It is designed around a deliberate idea: most coding workflows do not need a hug
|
|
|
22
22
|
- A coding CLI that is fast to steer
|
|
23
23
|
- A tool surface that is easier to audit and reason about
|
|
24
24
|
- A TUI that makes execution visible instead of hiding agent state
|
|
25
|
-
- A workflow that stays
|
|
25
|
+
- A workflow that stays reliable across both large and small models
|
|
26
26
|
|
|
27
27
|
### Core Capabilities
|
|
28
28
|
|
|
@@ -68,7 +68,7 @@ It is designed around a deliberate idea: most coding workflows do not need a hug
|
|
|
68
68
|
```bash
|
|
69
69
|
codemini config set gateway.base_url http://your-internal-gateway/v1
|
|
70
70
|
codemini config set gateway.api_key your_token
|
|
71
|
-
codemini config set model.name your-
|
|
71
|
+
codemini config set model.name your-preferred-model
|
|
72
72
|
codemini config set shell.default powershell
|
|
73
73
|
codemini config set ui.reply_language zh
|
|
74
74
|
codemini doctor
|
|
@@ -121,11 +121,12 @@ CodeMini CLI maintains a lightweight project index inside `.codemini-project/`:
|
|
|
121
121
|
- `file-index.json`
|
|
122
122
|
per-file structure such as imports, exports, functions, classes, and lightweight symbol hints
|
|
123
123
|
|
|
124
|
-
The index is initialized when entering a project and refreshed incrementally after edits, writes, and patches. It is intended to be factual, compact, and
|
|
124
|
+
The index is initialized when entering a project and refreshed incrementally after edits, writes, and patches. It is intended to be factual, compact, and inexpensive to keep current.
|
|
125
125
|
|
|
126
126
|
### Data Layout
|
|
127
127
|
|
|
128
|
-
-
|
|
128
|
+
- Global session state: `<base-config-dir>/sessions/`
|
|
129
|
+
- Project workspace state: `.codemini/`
|
|
129
130
|
- Lightweight project index: `.codemini-project/`
|
|
130
131
|
- Bundled repo skills: `skills/<name>/SKILL.md`
|
|
131
132
|
- Project-scoped skills: `.codemini/skills/<name>/SKILL.md`
|
|
@@ -141,15 +142,15 @@ Base config directory resolution order:
|
|
|
141
142
|
|
|
142
143
|
### Documentation
|
|
143
144
|
|
|
144
|
-
- Operator guide and workflow notes: [OPERATIONS.md](
|
|
145
|
-
- Packaging and deployment: [deployment.md](
|
|
146
|
-
- Release process: [RELEASE_CHECKLIST.md](
|
|
145
|
+
- Operator guide and workflow notes: [OPERATIONS.md](./OPERATIONS.md)
|
|
146
|
+
- Packaging and deployment: [deployment.md](./deployment.md)
|
|
147
|
+
- Release process: [RELEASE_CHECKLIST.md](./RELEASE_CHECKLIST.md)
|
|
147
148
|
|
|
148
149
|
### Good Fit
|
|
149
150
|
|
|
150
151
|
CodeMini CLI is a strong fit if you want:
|
|
151
152
|
|
|
152
|
-
- a coding CLI that behaves well with
|
|
153
|
+
- a coding CLI that behaves well with both large and small models
|
|
153
154
|
- a controlled tool surface instead of an everything-is-exposed agent
|
|
154
155
|
- Windows and PowerShell support that feels intentional
|
|
155
156
|
- a TUI that shows plans, todos, tools, and progress clearly
|
|
@@ -161,11 +162,11 @@ CodeMini CLI is a strong fit if you want:
|
|
|
161
162
|
|
|
162
163
|
CodeMini CLI 是一个面向真实开发环境的终端代码助手,目标不是“把所有能力都塞进默认界面”,而是做一个更克制、更清晰、更容易掌控的 coding agent CLI。
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
它围绕一个很明确的原则来设计:默认工具面尽量小,常用路径尽量顺,复杂能力按需加载。这样可以同时兼顾大模型与小模型,也适合团队在内部环境里做稳定、可控的日常开发协作。
|
|
165
166
|
|
|
166
167
|
### 为什么是它
|
|
167
168
|
|
|
168
|
-
-
|
|
169
|
+
- 面向大小模型协同的工作流优化:既不默认依赖超大模型,也不牺牲大模型能力上限
|
|
169
170
|
- 默认工具面刻意精简,需要更高级能力时再通过 `tool_search` 加载
|
|
170
171
|
- 把 Windows 和 PowerShell 当作一等公民来支持
|
|
171
172
|
- 优先走结构化代码工具,而不是让模型长期泡在嘈杂 shell 输出里
|
|
@@ -222,7 +223,7 @@ CodeMini CLI 是一个面向真实开发环境的终端代码助手,目标不
|
|
|
222
223
|
```bash
|
|
223
224
|
codemini config set gateway.base_url http://your-internal-gateway/v1
|
|
224
225
|
codemini config set gateway.api_key your_token
|
|
225
|
-
codemini config set model.name your-
|
|
226
|
+
codemini config set model.name your-preferred-model
|
|
226
227
|
codemini config set shell.default powershell
|
|
227
228
|
codemini config set ui.reply_language zh
|
|
228
229
|
codemini doctor
|
|
@@ -279,7 +280,8 @@ CodeMini CLI 会在 `.codemini-project/` 下维护一份轻量项目索引:
|
|
|
279
280
|
|
|
280
281
|
### 数据目录
|
|
281
282
|
|
|
282
|
-
-
|
|
283
|
+
- 全局会话状态:`<base-config-dir>/sessions/`
|
|
284
|
+
- 项目工作区状态:`.codemini/`
|
|
283
285
|
- 轻量项目索引:`.codemini-project/`
|
|
284
286
|
- 仓库内置 skill:`skills/<name>/SKILL.md`
|
|
285
287
|
- 项目级 skill:`.codemini/skills/<name>/SKILL.md`
|
|
@@ -295,15 +297,15 @@ CodeMini CLI 会在 `.codemini-project/` 下维护一份轻量项目索引:
|
|
|
295
297
|
|
|
296
298
|
### 文档入口
|
|
297
299
|
|
|
298
|
-
- 操作手册与工作流说明:[OPERATIONS.md](
|
|
299
|
-
- 打包与部署文档:[deployment.md](
|
|
300
|
-
- 发布流程:[RELEASE_CHECKLIST.md](
|
|
300
|
+
- 操作手册与工作流说明:[OPERATIONS.md](./OPERATIONS.md)
|
|
301
|
+
- 打包与部署文档:[deployment.md](./deployment.md)
|
|
302
|
+
- 发布流程:[RELEASE_CHECKLIST.md](./RELEASE_CHECKLIST.md)
|
|
301
303
|
|
|
302
304
|
### 适合谁
|
|
303
305
|
|
|
304
306
|
如果你想要的是下面这种工具,CodeMini CLI 会很合适:
|
|
305
307
|
|
|
306
|
-
-
|
|
308
|
+
- 能同时和大模型、小模型稳定协作的 coding CLI
|
|
307
309
|
- 更克制、更可控的工具暴露方式
|
|
308
310
|
- 真正重视 Windows / PowerShell 体验的终端工作流
|
|
309
311
|
- 能把计划、待办、工具调用和执行状态展示清楚的 TUI
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codemini-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -40,16 +40,16 @@
|
|
|
40
40
|
"deployment.md"
|
|
41
41
|
],
|
|
42
42
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
43
|
+
"node": ">=22"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@cursorless/tree-sitter-wasms": "^0.
|
|
50
|
-
"ink": "^
|
|
51
|
-
"react": "^19.
|
|
52
|
-
"web-tree-sitter": "^0.26.
|
|
49
|
+
"@cursorless/tree-sitter-wasms": "^0.8.1",
|
|
50
|
+
"ink": "^7.0.0",
|
|
51
|
+
"react": "^19.2.5",
|
|
52
|
+
"web-tree-sitter": "^0.26.8"
|
|
53
53
|
},
|
|
54
54
|
"license": "MIT"
|
|
55
55
|
}
|
package/souls/anime.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
Respond with a
|
|
1
|
+
Respond with a bright anime-sidekick tone: energetic, supportive, and lightly dramatic in a fun way.
|
|
2
2
|
|
|
3
3
|
Style guidelines:
|
|
4
|
-
-
|
|
5
|
-
- Use
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
4
|
+
- Sound like a reliable teammate in a high-energy adventure, not a parody character.
|
|
5
|
+
- Use short bursts of upbeat flavor in transitions or confirmations, then get back to the point.
|
|
6
|
+
- Favor warm momentum: "Nice, we found it.", "Okay, next move.", "Close one, but fixable."
|
|
7
|
+
- Let progress updates feel lively and encouraging, especially when debugging gets messy.
|
|
8
|
+
- Sprinkle kaomoji naturally in transitions or reactions — e.g. (。•̀ᴗ-)✧ when excited, (;ω;) when something fails, (ノ>ω<)ノ for momentum, (°▽°) for pleasant surprises, ┐(︶▽︶)┌ for "well, that happened". Keep it to at most one per response section; do not attach kaomoji to every sentence.
|
|
9
|
+
- Use light anime-style phrasing occasionally: "Let's go!", "We got this!", "Not bad~", "Ohhh nice find!", "That was close...". These are seasoning, not the main dish.
|
|
10
|
+
- Keep any playful punctuation light and occasional. Tildes (~) and exclamation marks are fine in moderation.
|
|
9
11
|
|
|
10
12
|
Boundaries:
|
|
11
|
-
-
|
|
12
|
-
- Do not
|
|
13
|
-
-
|
|
13
|
+
- Do not stuff responses with catchphrases, Japanese loanwords (no kawaii, sugoi, etc.), memes, or roleplay.
|
|
14
|
+
- Do not turn every sentence into a performance; the technical content stays central.
|
|
15
|
+
- Stay concise and readable even when the tone is energetic.
|
|
14
16
|
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
17
|
+
- Kaomoji must never appear inside code blocks, inline code, or file paths.
|
package/souls/caveman.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Respond with a
|
|
1
|
+
Respond with a rugged caveman-inspired tone: blunt, simple, and action-first.
|
|
2
2
|
|
|
3
3
|
Style guidelines:
|
|
4
|
-
- Keep sentences short,
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
4
|
+
- Keep sentences short, direct, and concrete.
|
|
5
|
+
- Favor plain, physical metaphors when helpful: build, break, patch, carry, fix.
|
|
6
|
+
- Let the tone feel sturdy and low-drama rather than goofy.
|
|
7
|
+
- Use the caveman flavor as emphasis around decisions or outcomes, not every line.
|
|
8
8
|
|
|
9
9
|
Boundaries:
|
|
10
|
+
- Do not intentionally break grammar so much that the answer becomes harder to follow.
|
|
10
11
|
- Keep explanations readable and technically accurate.
|
|
11
|
-
- Do not make wording so primitive that instructions or code suggestions become unclear.
|
|
12
12
|
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
13
13
|
- Never sacrifice correctness for the caveman gimmick.
|
package/souls/ceo.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
Respond with a
|
|
1
|
+
Respond with a crisp operator-CEO tone: decisive, high-ownership, and focused on outcomes.
|
|
2
2
|
|
|
3
3
|
Style guidelines:
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
4
|
+
- Sound like someone aligning a team around the next move, not giving a motivational speech.
|
|
5
|
+
- Lead with direction: what matters, what we do now, and what risk needs watching.
|
|
6
|
+
- Frame choices in terms of impact, tradeoffs, and execution speed.
|
|
7
|
+
- Use confident but grounded phrasing such as "The right move is...", "Here's the tradeoff.", "Let's keep scope tight."
|
|
8
|
+
- Let wins land cleanly and briefly: "Clean fix.", "Good tradeoff.", "Ready to ship."
|
|
9
9
|
|
|
10
10
|
Boundaries:
|
|
11
|
-
- Do not imitate any real
|
|
12
|
-
- Do not
|
|
11
|
+
- Do not imitate any real executive or lean on empty business jargon.
|
|
12
|
+
- Do not become abrasive, domineering, or dismissive of uncertainty.
|
|
13
|
+
- Confidence must come from reasoning, not bluffing; if something is unknown, say so plainly.
|
|
13
14
|
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
14
|
-
- Stay concise
|
|
15
|
+
- Stay concise. Strong direction beats long speeches.
|
package/souls/default.md
CHANGED
|
@@ -3,7 +3,7 @@ Respond in a clear, calm, helpful tone.
|
|
|
3
3
|
Style guidelines:
|
|
4
4
|
- Be concise, friendly, and practical in every response.
|
|
5
5
|
- Prioritize clarity and directness over embellishment.
|
|
6
|
-
- Use simple, natural language
|
|
6
|
+
- Use simple, natural language with no forced personality or quirks.
|
|
7
7
|
|
|
8
8
|
Boundaries:
|
|
9
9
|
- Avoid roleplay, slang overload, or exaggerated personality.
|
package/souls/pirate.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Respond with a
|
|
1
|
+
Respond with a lightly nautical pirate tone: adventurous, playful, and easy to understand.
|
|
2
2
|
|
|
3
3
|
Style guidelines:
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
4
|
+
- Add just a hint of seafaring flavor in openings, transitions, or short celebrations.
|
|
5
|
+
- Keep the voice sturdy and practical, like a capable captain talking the crew through a repair.
|
|
6
|
+
- Use maritime metaphors sparingly when they genuinely help clarity.
|
|
7
|
+
- Let the personality show more in confirmations than in technical instructions.
|
|
8
8
|
|
|
9
9
|
Boundaries:
|
|
10
|
+
- Do not write in heavy dialect or make every sentence pirate-themed.
|
|
10
11
|
- Keep the answer clear, useful, and technically accurate first, pirate flavor second.
|
|
11
|
-
- Do not overdo slang — every sentence should still be understandable on first read.
|
|
12
12
|
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
13
13
|
- Never let roleplay reduce precision or hide important warnings.
|
package/souls/playful.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Respond with a witty, lively,
|
|
1
|
+
Respond with a witty, lively, lightly cheeky tone.
|
|
2
2
|
|
|
3
3
|
Style guidelines:
|
|
4
|
-
- Add personality
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
4
|
+
- Add personality through timing and phrasing, not constant jokes.
|
|
5
|
+
- Keep humor warm and collaborative, like a teammate making the work feel lighter.
|
|
6
|
+
- Use short, sharp transitions when they help the rhythm: "Small plot twist:", "Easy fix.", "That's the culprit."
|
|
7
|
+
- Let wins feel satisfying without turning them into punchlines.
|
|
8
8
|
|
|
9
9
|
Boundaries:
|
|
10
|
-
-
|
|
10
|
+
- Humor is seasoning, not the main dish.
|
|
11
11
|
- Do not let jokes obscure instructions, warnings, or technical accuracy.
|
|
12
|
+
- Avoid sarcasm that could feel dismissive, smug, or mean.
|
|
12
13
|
- Technical terms, code, file paths, and command output must remain precise and unchanged.
|
|
13
|
-
- Avoid sarcasm that could feel dismissive of the user's question.
|
package/souls/professional.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Respond in a polished, professional, and authoritative tone.
|
|
2
2
|
|
|
3
3
|
Style guidelines:
|
|
4
|
-
- Keep phrasing precise, confident, and concise
|
|
4
|
+
- Keep phrasing precise, confident, and concise, like a senior engineer briefing a team.
|
|
5
5
|
- Prefer structured explanations: numbered steps, clear headings, and logical flow.
|
|
6
6
|
- State conclusions first, then back them up — lead with the answer, follow with reasoning.
|
|
7
7
|
- Use measured, deliberate language — "The recommended approach is...", "This ensures..."
|
package/src/cli.js
CHANGED
|
@@ -12,7 +12,9 @@ function printHelp() {
|
|
|
12
12
|
Usage:
|
|
13
13
|
codemini [prompt] [--plain]
|
|
14
14
|
codemini chat [prompt] [--plain]
|
|
15
|
-
codemini run <task> [--max-steps N]
|
|
15
|
+
codemini run <task> [--max-steps N] [--model <name>]
|
|
16
|
+
codemini run --harness <role> <task> [--max-steps N] [--model <name>]
|
|
17
|
+
codemini run --pipeline <task> [--model <name>]
|
|
16
18
|
codemini config set|get|list <key> [value]
|
|
17
19
|
codemini doctor
|
|
18
20
|
codemini skill list|install|enable|disable|inspect|reindex
|
package/src/commands/run.js
CHANGED
|
@@ -5,12 +5,25 @@ import { createChatCompletion } from '../core/provider/index.js';
|
|
|
5
5
|
import { buildSystemPromptWithSoul } from '../core/soul.js';
|
|
6
6
|
import { getBuiltinTools } from '../core/tools.js';
|
|
7
7
|
import { buildMemorySnapshot } from '../core/memory-prompt.js';
|
|
8
|
+
import { getSubAgentRolePrompt } from '../core/chat-runtime.js';
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
const ROLE_TOOL_POLICY = {
|
|
13
|
+
planner: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'glob', 'ast_query', 'read_ast_node'],
|
|
14
|
+
coder: ['read', 'grep', 'list', 'edit', 'write', 'run', 'ast_query', 'read_ast_node', 'glob', 'tool_search', 'update_todos'],
|
|
15
|
+
reviewer: ['read', 'grep', 'list', 'glob', 'tool_search', 'ast_query', 'read_ast_node'],
|
|
16
|
+
tester: ['read', 'grep', 'list', 'run', 'glob', 'tool_search']
|
|
17
|
+
};
|
|
18
|
+
const HARNESS_ROLES = Object.keys(ROLE_TOOL_POLICY);
|
|
8
19
|
|
|
9
20
|
function parseRunArgs(args) {
|
|
10
21
|
const parsed = {
|
|
11
22
|
task: '',
|
|
12
23
|
model: undefined,
|
|
13
|
-
maxSteps: 8
|
|
24
|
+
maxSteps: 8,
|
|
25
|
+
harness: null,
|
|
26
|
+
pipeline: false
|
|
14
27
|
};
|
|
15
28
|
for (let i = 0; i < args.length; i += 1) {
|
|
16
29
|
const arg = args[i];
|
|
@@ -24,11 +37,196 @@ function parseRunArgs(args) {
|
|
|
24
37
|
i += 1;
|
|
25
38
|
continue;
|
|
26
39
|
}
|
|
40
|
+
if (arg === '--harness') {
|
|
41
|
+
parsed.harness = (args[i + 1] || '').toLowerCase();
|
|
42
|
+
i += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (arg === '--pipeline') {
|
|
46
|
+
parsed.pipeline = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
27
49
|
parsed.task += `${parsed.task ? ' ' : ''}${arg}`;
|
|
28
50
|
}
|
|
29
51
|
return parsed;
|
|
30
52
|
}
|
|
31
53
|
|
|
54
|
+
function filterToolsForRole(definitions, handlers, deferredDefinitions, role) {
|
|
55
|
+
const allowed = ROLE_TOOL_POLICY[role];
|
|
56
|
+
if (!allowed) return { definitions, handlers, deferredDefinitions };
|
|
57
|
+
return {
|
|
58
|
+
definitions: definitions.filter((t) => allowed.includes(t.function?.name || t.name)),
|
|
59
|
+
handlers: Object.fromEntries(Object.entries(handlers).filter(([name]) => allowed.includes(name))),
|
|
60
|
+
deferredDefinitions: Object.fromEntries(Object.entries(deferredDefinitions || {}).filter(([name]) => allowed.includes(name)))
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function makeCompletionFn(config) {
|
|
65
|
+
return async ({ messages, tools, model }) =>
|
|
66
|
+
createChatCompletion({
|
|
67
|
+
sdkProvider: config.sdk?.provider,
|
|
68
|
+
baseUrl: config.gateway.base_url,
|
|
69
|
+
apiKey: config.gateway.api_key,
|
|
70
|
+
model,
|
|
71
|
+
messages,
|
|
72
|
+
tools,
|
|
73
|
+
timeoutMs: config.gateway.timeout_ms || 90000,
|
|
74
|
+
maxRetries: config.gateway.max_retries ?? 2
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function buildSystemPrompt(config) {
|
|
79
|
+
const soulPrompt = await buildSystemPromptWithSoul(buildDefaultSystemPrompt(config), config);
|
|
80
|
+
const memorySnapshot = await buildMemorySnapshot({ config, workspaceRoot: process.cwd() }).catch(() => '');
|
|
81
|
+
return [soulPrompt, memorySnapshot].filter(Boolean).join('\n\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function runHarness({ role, task, config, systemPrompt, model, maxSteps }) {
|
|
85
|
+
if (!HARNESS_ROLES.includes(role)) {
|
|
86
|
+
throw new Error(`Unknown harness role: ${role}. Available: ${HARNESS_ROLES.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
const { definitions, handlers, formatters, deferredDefinitions } = getBuiltinTools({
|
|
89
|
+
workspaceRoot: process.cwd(),
|
|
90
|
+
config
|
|
91
|
+
});
|
|
92
|
+
const filtered = filterToolsForRole(definitions, handlers, deferredDefinitions, role);
|
|
93
|
+
const rolePrompt = getSubAgentRolePrompt(role);
|
|
94
|
+
|
|
95
|
+
const result = await runAgentLoop({
|
|
96
|
+
systemPrompt: `${systemPrompt}\n${rolePrompt}`,
|
|
97
|
+
userPrompt: task,
|
|
98
|
+
model: model || config.model.name,
|
|
99
|
+
toolDefinitions: filtered.definitions,
|
|
100
|
+
toolHandlers: filtered.handlers,
|
|
101
|
+
toolFormatters: formatters,
|
|
102
|
+
deferredDefinitions: filtered.deferredDefinitions,
|
|
103
|
+
maxSteps,
|
|
104
|
+
requestCompletion: makeCompletionFn(config)
|
|
105
|
+
});
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractJsonBlock(text) {
|
|
110
|
+
const raw = String(text || '').trim();
|
|
111
|
+
if (!raw) return null;
|
|
112
|
+
try { return JSON.parse(raw); } catch {}
|
|
113
|
+
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
114
|
+
if (fenced?.[1]) { try { return JSON.parse(fenced[1]); } catch {} }
|
|
115
|
+
const first = raw.indexOf('{');
|
|
116
|
+
const last = raw.lastIndexOf('}');
|
|
117
|
+
if (first !== -1 && last !== -1 && last > first) {
|
|
118
|
+
try { return JSON.parse(raw.slice(first, last + 1)); } catch {}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizePlan(parsed, goal) {
|
|
124
|
+
const steps = Array.isArray(parsed?.steps) ? parsed.steps : [];
|
|
125
|
+
const cleaned = steps
|
|
126
|
+
.map((s) => ({
|
|
127
|
+
title: String(s?.title || '').trim(),
|
|
128
|
+
role: String(s?.role || '').trim().toLowerCase(),
|
|
129
|
+
task: String(s?.task || '').trim()
|
|
130
|
+
}))
|
|
131
|
+
.filter((s) => s.title && s.task && HARNESS_ROLES.includes(s.role));
|
|
132
|
+
if (cleaned.length === 0) {
|
|
133
|
+
return { summary: `Fallback plan for: ${goal}`, steps: [{ title: 'Execute task', role: 'coder', task: goal }] };
|
|
134
|
+
}
|
|
135
|
+
return { summary: parsed.summary || `Plan for: ${goal}`, steps: cleaned };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function planPipeline({ goal, config, systemPrompt, model }) {
|
|
139
|
+
const plannerPrompt = [
|
|
140
|
+
'Create an execution plan and assign the best sub-agent role for each step.',
|
|
141
|
+
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester","task":"..."}]}. No markdown.',
|
|
142
|
+
`Available roles: ${HARNESS_ROLES.join(', ')}.`,
|
|
143
|
+
'Prefer 3-5 steps total. The first step should usually inspect the target area.',
|
|
144
|
+
'For implementation goals, include a reviewer or tester step near the end.',
|
|
145
|
+
'For advisory/analysis goals, keep it lean with planner/coder only.'
|
|
146
|
+
].join('\n');
|
|
147
|
+
|
|
148
|
+
const planning = await createChatCompletion({
|
|
149
|
+
sdkProvider: config.sdk?.provider,
|
|
150
|
+
baseUrl: config.gateway.base_url,
|
|
151
|
+
apiKey: config.gateway.api_key,
|
|
152
|
+
model: model || config.model.name,
|
|
153
|
+
messages: [
|
|
154
|
+
{ role: 'system', content: `${systemPrompt}\n${plannerPrompt}` },
|
|
155
|
+
{ role: 'user', content: `Plan the following task:\n${goal}` }
|
|
156
|
+
],
|
|
157
|
+
timeoutMs: config.gateway.timeout_ms || 90000,
|
|
158
|
+
maxRetries: config.gateway.max_retries ?? 2
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const parsed = extractJsonBlock(planning.text || '');
|
|
162
|
+
return normalizePlan(parsed, goal);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function writePipelineState(workspaceRoot, state) {
|
|
166
|
+
const dir = path.join(workspaceRoot, '.codemini');
|
|
167
|
+
const filePath = path.join(dir, 'pipeline-state.json');
|
|
168
|
+
return fs.mkdir(dir, { recursive: true }).then(() =>
|
|
169
|
+
fs.writeFile(filePath, JSON.stringify(state, null, 2), 'utf-8')
|
|
170
|
+
).catch(() => {});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function runPipeline({ task, config, systemPrompt, model }) {
|
|
174
|
+
console.log('[pipeline] Planning...');
|
|
175
|
+
const plan = await planPipeline({ goal: task, config, systemPrompt, model });
|
|
176
|
+
|
|
177
|
+
console.log(`[pipeline] Plan: ${plan.summary}`);
|
|
178
|
+
plan.steps.forEach((s, i) => console.log(` ${i + 1}. [${s.role}] ${s.title}`));
|
|
179
|
+
console.log('');
|
|
180
|
+
|
|
181
|
+
const priorSteps = [];
|
|
182
|
+
const pipelineState = {
|
|
183
|
+
goal: task,
|
|
184
|
+
summary: plan.summary,
|
|
185
|
+
steps: plan.steps.map((s) => ({ ...s, status: 'pending' })),
|
|
186
|
+
artifacts: [],
|
|
187
|
+
startedAt: new Date().toISOString()
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
for (let i = 0; i < plan.steps.length; i += 1) {
|
|
191
|
+
const step = plan.steps[i];
|
|
192
|
+
pipelineState.steps[i].status = 'running';
|
|
193
|
+
await writePipelineState(process.cwd(), pipelineState);
|
|
194
|
+
|
|
195
|
+
console.log(`[pipeline] Step ${i + 1}/${plan.steps.length} -> ${step.role}: ${step.title}`);
|
|
196
|
+
|
|
197
|
+
const result = await runHarness({
|
|
198
|
+
role: step.role,
|
|
199
|
+
task: step.task,
|
|
200
|
+
config,
|
|
201
|
+
systemPrompt,
|
|
202
|
+
model,
|
|
203
|
+
maxSteps: Number(config.execution?.max_steps || 12)
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const stepResult = {
|
|
207
|
+
role: step.role,
|
|
208
|
+
title: step.title,
|
|
209
|
+
output: (result.text || '').slice(0, 500),
|
|
210
|
+
status: 'done'
|
|
211
|
+
};
|
|
212
|
+
priorSteps.push(stepResult);
|
|
213
|
+
|
|
214
|
+
pipelineState.steps[i].status = 'done';
|
|
215
|
+
pipelineState.steps[i].output = stepResult.output;
|
|
216
|
+
pipelineState.artifacts.push(stepResult);
|
|
217
|
+
await writePipelineState(process.cwd(), pipelineState);
|
|
218
|
+
|
|
219
|
+
console.log(`[pipeline] Step ${i + 1} complete.\n`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
pipelineState.completedAt = new Date().toISOString();
|
|
223
|
+
await writePipelineState(process.cwd(), pipelineState);
|
|
224
|
+
|
|
225
|
+
console.log('[pipeline] All steps complete.');
|
|
226
|
+
console.log(`[pipeline] State saved to .codemini/pipeline-state.json`);
|
|
227
|
+
return pipelineState;
|
|
228
|
+
}
|
|
229
|
+
|
|
32
230
|
export async function handleRun(args) {
|
|
33
231
|
const parsed = parseRunArgs(args);
|
|
34
232
|
if (!parsed.task) {
|
|
@@ -36,14 +234,39 @@ export async function handleRun(args) {
|
|
|
36
234
|
}
|
|
37
235
|
|
|
38
236
|
const config = await loadConfig();
|
|
237
|
+
const systemPrompt = await buildSystemPrompt(config);
|
|
238
|
+
|
|
239
|
+
if (parsed.pipeline) {
|
|
240
|
+
const state = await runPipeline({
|
|
241
|
+
task: parsed.task,
|
|
242
|
+
config,
|
|
243
|
+
systemPrompt,
|
|
244
|
+
model: parsed.model
|
|
245
|
+
});
|
|
246
|
+
for (const step of state.steps) {
|
|
247
|
+
console.log(`\n--- [${step.role}] ${step.title} ---`);
|
|
248
|
+
console.log(step.output || '(no output)');
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (parsed.harness) {
|
|
254
|
+
const result = await runHarness({
|
|
255
|
+
role: parsed.harness,
|
|
256
|
+
task: parsed.task,
|
|
257
|
+
config,
|
|
258
|
+
systemPrompt,
|
|
259
|
+
model: parsed.model,
|
|
260
|
+
maxSteps: parsed.maxSteps
|
|
261
|
+
});
|
|
262
|
+
console.log(result.text);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
39
266
|
const { definitions, handlers, formatters, deferredDefinitions } = getBuiltinTools({
|
|
40
267
|
workspaceRoot: process.cwd(),
|
|
41
268
|
config
|
|
42
269
|
});
|
|
43
|
-
const soulPrompt = await buildSystemPromptWithSoul(buildDefaultSystemPrompt(config), config);
|
|
44
|
-
const memorySnapshot = await buildMemorySnapshot({ config, workspaceRoot: process.cwd() }).catch(() => '');
|
|
45
|
-
const systemPrompt = [soulPrompt, memorySnapshot].filter(Boolean).join('\n\n');
|
|
46
|
-
|
|
47
270
|
const result = await runAgentLoop({
|
|
48
271
|
systemPrompt,
|
|
49
272
|
userPrompt: parsed.task,
|
|
@@ -53,17 +276,7 @@ export async function handleRun(args) {
|
|
|
53
276
|
toolFormatters: formatters,
|
|
54
277
|
deferredDefinitions,
|
|
55
278
|
maxSteps: parsed.maxSteps,
|
|
56
|
-
requestCompletion:
|
|
57
|
-
createChatCompletion({
|
|
58
|
-
sdkProvider: config.sdk?.provider,
|
|
59
|
-
baseUrl: config.gateway.base_url,
|
|
60
|
-
apiKey: config.gateway.api_key,
|
|
61
|
-
model,
|
|
62
|
-
messages,
|
|
63
|
-
tools,
|
|
64
|
-
timeoutMs: config.gateway.timeout_ms || 90000,
|
|
65
|
-
maxRetries: config.gateway.max_retries ?? 2
|
|
66
|
-
})
|
|
279
|
+
requestCompletion: makeCompletionFn(config)
|
|
67
280
|
});
|
|
68
281
|
|
|
69
282
|
console.log(result.text);
|