@kkelly-offical/kkcode 0.1.6 → 0.2.1
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/LICENSE +674 -674
- package/README.md +452 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +19 -2
- package/src/agent/custom-agent-loader.mjs +6 -3
- package/src/agent/generator.mjs +2 -2
- package/src/agent/prompt/assistant.txt +12 -0
- package/src/agent/prompt/bug-hunter.txt +90 -0
- package/src/agent/prompt/frontend-designer.txt +58 -58
- package/src/agent/prompt/guide.txt +1 -1
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
- package/src/agent/prompt/longagent-coding-agent.txt +37 -37
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
- package/src/agent/prompt/longagent-preview-agent.txt +63 -63
- package/src/command/custom-commands.mjs +2 -2
- package/src/commands/agent.mjs +1 -1
- package/src/commands/background.mjs +145 -4
- package/src/commands/chat.mjs +117 -76
- package/src/commands/config.mjs +148 -1
- package/src/commands/doctor.mjs +30 -6
- package/src/commands/init.mjs +32 -6
- package/src/commands/longagent.mjs +117 -0
- package/src/commands/mcp.mjs +275 -43
- package/src/commands/permission.mjs +1 -1
- package/src/commands/session.mjs +195 -140
- package/src/commands/skill.mjs +63 -0
- package/src/commands/theme.mjs +1 -1
- package/src/config/defaults.mjs +280 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +591 -574
- package/src/context.mjs +4 -1
- package/src/core/constants.mjs +97 -91
- package/src/core/types.mjs +1 -1
- package/src/github/api.mjs +78 -78
- package/src/github/auth.mjs +294 -286
- package/src/github/flow.mjs +298 -298
- package/src/github/workspace.mjs +225 -212
- package/src/index.mjs +84 -82
- package/src/knowledge/frontend-aesthetics.txt +38 -38
- package/src/mcp/client-http.mjs +139 -141
- package/src/mcp/client-sse.mjs +297 -288
- package/src/mcp/client-stdio.mjs +534 -533
- package/src/mcp/constants.mjs +2 -2
- package/src/mcp/registry.mjs +498 -479
- package/src/mcp/stdio-framing.mjs +135 -133
- package/src/mcp/tool-result.mjs +24 -24
- package/src/observability/edit-diagnostics.mjs +449 -0
- package/src/observability/index.mjs +42 -42
- package/src/observability/metrics.mjs +165 -137
- package/src/observability/tracer.mjs +137 -137
- package/src/onboarding.mjs +209 -0
- package/src/orchestration/background-manager.mjs +567 -372
- package/src/orchestration/background-worker.mjs +419 -305
- package/src/orchestration/interruption-reason.mjs +21 -0
- package/src/orchestration/longagent-manager.mjs +197 -171
- package/src/orchestration/stage-scheduler.mjs +733 -728
- package/src/orchestration/subagent-router.mjs +7 -1
- package/src/orchestration/task-scheduler.mjs +219 -7
- package/src/permission/engine.mjs +1 -1
- package/src/permission/exec-policy.mjs +370 -370
- package/src/permission/file-edit-policy.mjs +108 -0
- package/src/permission/prompt.mjs +1 -1
- package/src/permission/rules.mjs +116 -7
- package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
- package/src/plugin/hook-bus.mjs +19 -5
- package/src/plugin/manifest-loader.mjs +222 -0
- package/src/provider/anthropic.mjs +396 -390
- package/src/provider/ollama.mjs +7 -1
- package/src/provider/openai.mjs +382 -340
- package/src/provider/retry-policy.mjs +74 -68
- package/src/provider/router.mjs +242 -241
- package/src/provider/sse.mjs +104 -104
- package/src/provider/wizard.mjs +556 -0
- package/src/repl/capability-facade.mjs +30 -0
- package/src/repl/command-surface.mjs +23 -0
- package/src/repl/controller-entry.mjs +40 -0
- package/src/repl/core-shell.mjs +208 -0
- package/src/repl/dialog-router.mjs +87 -0
- package/src/repl/input-engine.mjs +76 -0
- package/src/repl/keymap.mjs +7 -0
- package/src/repl/operator-surface.mjs +15 -0
- package/src/repl/permission-flow.mjs +49 -0
- package/src/repl/runtime-facade.mjs +36 -0
- package/src/repl/slash-router.mjs +62 -0
- package/src/repl/state-store.mjs +29 -0
- package/src/repl/turn-controller.mjs +58 -0
- package/src/repl/verification.mjs +23 -0
- package/src/repl.mjs +3368 -2929
- package/src/rules/load-rules.mjs +3 -3
- package/src/runtime.mjs +1 -1
- package/src/session/agent-transaction.mjs +86 -0
- package/src/session/checkpoint.mjs +302 -302
- package/src/session/compaction.mjs +36 -14
- package/src/session/engine.mjs +417 -227
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1081
- package/src/session/longagent-plan.mjs +376 -365
- package/src/session/longagent-project-memory.mjs +53 -53
- package/src/session/longagent-scaffold.mjs +291 -291
- package/src/session/longagent-task-bus.mjs +138 -54
- package/src/session/longagent-utils.mjs +828 -472
- package/src/session/longagent.mjs +911 -884
- package/src/session/loop.mjs +1005 -905
- package/src/session/prompt/agent.txt +25 -0
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +28 -6
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +197 -0
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -510
- package/src/session/system-prompt.mjs +56 -8
- package/src/session/task-validator.mjs +267 -267
- package/src/session/usability-gates.mjs +2 -2
- package/src/skill/builtin/commit.mjs +64 -64
- package/src/skill/builtin/design.mjs +76 -76
- package/src/skill/generator.mjs +18 -2
- package/src/skill/registry.mjs +642 -390
- package/src/storage/audit-store.mjs +18 -11
- package/src/storage/event-log.mjs +7 -1
- package/src/storage/ghost-commit-store.mjs +243 -245
- package/src/storage/paths.mjs +13 -0
- package/src/theme/default-theme.mjs +1 -1
- package/src/theme/markdown.mjs +4 -0
- package/src/theme/schema.mjs +1 -1
- package/src/theme/status-bar.mjs +162 -158
- package/src/tool/audit-wrapper.mjs +18 -2
- package/src/tool/edit-transaction.mjs +23 -0
- package/src/tool/executor.mjs +26 -1
- package/src/tool/file-read-state.mjs +65 -0
- package/src/tool/git-auto.mjs +526 -526
- package/src/tool/git-full-auto.mjs +487 -478
- package/src/tool/mutation-guard.mjs +54 -0
- package/src/tool/prompt/edit.txt +3 -3
- package/src/tool/prompt/multiedit.txt +1 -0
- package/src/tool/prompt/notebookedit.txt +2 -1
- package/src/tool/prompt/patch.txt +25 -24
- package/src/tool/prompt/read.txt +3 -3
- package/src/tool/prompt/sysinfo.txt +29 -0
- package/src/tool/prompt/task.txt +66 -4
- package/src/tool/prompt/write.txt +2 -2
- package/src/tool/question-prompt.mjs +17 -4
- package/src/tool/registry.mjs +1701 -1343
- package/src/tool/task-tool.mjs +14 -6
- package/src/ui/activity-renderer.mjs +667 -664
- package/src/ui/repl-background-panel.mjs +7 -0
- package/src/ui/repl-capability-panel.mjs +9 -0
- package/src/ui/repl-dashboard.mjs +54 -4
- package/src/ui/repl-help.mjs +110 -0
- package/src/ui/repl-operator-panel.mjs +12 -0
- package/src/ui/repl-route-feedback.mjs +35 -0
- package/src/ui/repl-status-view.mjs +76 -0
- package/src/ui/repl-task-panel.mjs +5 -0
- package/src/ui/repl-transcript-panel.mjs +56 -0
- package/src/ui/repl-turn-summary.mjs +135 -0
- package/src/usage/pricing.mjs +122 -121
- package/src/usage/usage-meter.mjs +1 -0
- package/src/util/git.mjs +562 -519
- package/src/util/template.mjs +6 -1
|
@@ -1,460 +1,467 @@
|
|
|
1
|
-
import { LongAgentManager } from "../orchestration/longagent-manager.mjs"
|
|
2
|
-
import { processTurnLoop } from "./loop.mjs"
|
|
3
|
-
import { markSessionStatus } from "./store.mjs"
|
|
4
|
-
import { EventBus } from "../core/events.mjs"
|
|
5
|
-
import {
|
|
6
|
-
EVENT_TYPES,
|
|
7
|
-
LONGAGENT_4STAGE_STAGES
|
|
8
|
-
} from "../core/constants.mjs"
|
|
9
|
-
import { saveCheckpoint } from "./checkpoint.mjs"
|
|
10
|
-
import { getAgent } from "../agent/agent.mjs"
|
|
11
|
-
import { createStuckTracker, isReadOnlyTool } from "./longagent-utils.mjs"
|
|
12
|
-
import * as git from "../util/git.mjs"
|
|
13
|
-
|
|
14
|
-
export function detectStageComplete(text, stage) {
|
|
15
|
-
const str = String(text || "")
|
|
16
|
-
const markers = {
|
|
17
|
-
[LONGAGENT_4STAGE_STAGES.PREVIEW]: /\[STAGE 1\/4: PREVIEW(?:ING AGENT)? - COMPLETE\]/,
|
|
18
|
-
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: /\[STAGE 2\/4: BLUEPRINT(?:\s+AGENT)? - COMPLETE\]/,
|
|
19
|
-
[LONGAGENT_4STAGE_STAGES.CODING]: /\[STAGE 3\/4: CODING(?:\s+AGENT)? - COMPLETE\]/,
|
|
20
|
-
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: /\[STAGE 4\/4: DEBUGGING(?:\s+AGENT)? - COMPLETE\]/
|
|
21
|
-
}
|
|
22
|
-
return markers[stage] ? markers[stage].test(str) : false
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function detectReturnToCoding(text) {
|
|
26
|
-
return /\[RETURN TO STAGE 3/.test(String(text || ""))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function buildStageWrapper(stage, context, userPrompt, warningMsg = null) {
|
|
30
|
-
const stageInfo = {
|
|
31
|
-
[LONGAGENT_4STAGE_STAGES.PREVIEW]: { num: "1/4", name: "PREVIEW", focus: "Explore project, understand requirements, extract key information", readonly: true },
|
|
32
|
-
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: { num: "2/4", name: "BLUEPRINT", focus: "Detailed planning, architecture design, function definitions", readonly: true },
|
|
33
|
-
[LONGAGENT_4STAGE_STAGES.CODING]: { num: "3/4", name: "CODING", focus: "Implement code strictly according to blueprint", readonly: false },
|
|
34
|
-
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: { num: "4/4", name: "DEBUGGING", focus: "Verify implementation, test, debug, validate completion", readonly: false }
|
|
35
|
-
}
|
|
36
|
-
const info = stageInfo[stage]
|
|
37
|
-
const parts = [
|
|
38
|
-
`=== LONGAGENT STAGE ${info.num}: ${info.name} ===`,
|
|
39
|
-
"",
|
|
40
|
-
`# STAGE OBJECTIVE: ${info.focus}`,
|
|
41
|
-
"",
|
|
42
|
-
`IMPORTANT: You are in STAGE ${info.num} of the four-stage LongAgent workflow.`,
|
|
43
|
-
""
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
if (info.readonly) {
|
|
47
|
-
parts.push(
|
|
48
|
-
"## PERMISSION CONSTRAINTS",
|
|
49
|
-
"YOU ARE IN READ-ONLY MODE FOR THIS STAGE.",
|
|
50
|
-
"- You MAY use: read, glob, grep, list, bash, question, todowrite",
|
|
51
|
-
"- You MUST NOT use: write, edit, patch, or any file modification tools",
|
|
52
|
-
""
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
parts.push("## YOUR TASKS FOR THIS STAGE:")
|
|
57
|
-
if (stage === LONGAGENT_4STAGE_STAGES.PREVIEW) {
|
|
58
|
-
parts.push(
|
|
59
|
-
"### 1. Project Structure Discovery",
|
|
60
|
-
"- Use glob to map the FULL directory tree (src/, test/, config/, scripts/, etc.)",
|
|
61
|
-
"- Identify the build system (package.json scripts, Makefile, Cargo.toml, etc.)",
|
|
62
|
-
"- Identify the test framework and test file naming convention",
|
|
63
|
-
"- Read the entry point(s) and trace the module dependency graph",
|
|
64
|
-
"",
|
|
65
|
-
"### 2. Technology Stack Audit",
|
|
66
|
-
"- Read package.json / requirements.txt / go.mod to catalog ALL dependencies",
|
|
67
|
-
"- Identify the runtime version constraints (engines, python_requires, etc.)",
|
|
68
|
-
"- Note the code style: ESM vs CJS, TypeScript vs JS, async patterns, error handling conventions",
|
|
69
|
-
"- Check for existing linter/formatter config (.eslintrc, .prettierrc, pyproject.toml)",
|
|
70
|
-
"",
|
|
71
|
-
"### 3. Requirement Decomposition",
|
|
72
|
-
"- Break the user objective into discrete, testable sub-requirements",
|
|
73
|
-
"- For each sub-requirement, identify which existing modules are affected",
|
|
74
|
-
"- Flag any ambiguities or missing information that could cause parallel agents to conflict",
|
|
75
|
-
"- Identify external API contracts, data schemas, or protocols involved",
|
|
76
|
-
"",
|
|
77
|
-
"### 4. Reuse & Risk Assessment",
|
|
78
|
-
"- List existing utilities, helpers, and abstractions that MUST be reused (do NOT reinvent)",
|
|
79
|
-
"- Identify files that are heavily imported — changes to these have high blast radius",
|
|
80
|
-
"- Note any existing tests that cover the affected modules (these must not regress)",
|
|
81
|
-
"- Flag potential conflicts: concurrent file access, circular dependencies, breaking API changes",
|
|
82
|
-
"",
|
|
83
|
-
"### 5. Output Format",
|
|
84
|
-
"Produce a structured findings report with these sections:",
|
|
85
|
-
"- **Tech Stack**: runtime, framework, key dependencies, build tool",
|
|
86
|
-
"- **Affected Modules**: list of files/directories that will be touched",
|
|
87
|
-
"- **Reusable Assets**: existing code to leverage",
|
|
88
|
-
"- **Risks**: potential issues, breaking changes, high-blast-radius files",
|
|
89
|
-
"- **Sub-requirements**: numbered list of discrete tasks derived from the objective"
|
|
90
|
-
)
|
|
91
|
-
} else if (stage === LONGAGENT_4STAGE_STAGES.BLUEPRINT) {
|
|
92
|
-
parts.push(
|
|
93
|
-
"### 1. Architecture Design",
|
|
94
|
-
"- Define the module boundaries: which new files to create, which existing files to modify",
|
|
95
|
-
"- For each new module: purpose, public API (exported functions/classes with signatures), internal structure",
|
|
96
|
-
"- For each modified module: what changes, what stays, backward compatibility impact",
|
|
97
|
-
"- Draw the dependency graph: A imports B, B imports C — ensure no circular dependencies",
|
|
98
|
-
"",
|
|
99
|
-
"### 2. Interface Contracts",
|
|
100
|
-
"- Define ALL function signatures with parameter types and return types",
|
|
101
|
-
"- Define data structures / schemas (object shapes, DB schemas, API request/response formats)",
|
|
102
|
-
"- Specify error types: what errors can each function throw, how callers should handle them",
|
|
103
|
-
"- Define event contracts if using pub/sub or EventEmitter patterns",
|
|
104
|
-
"",
|
|
105
|
-
"### 3. File Ownership & Parallelization Plan",
|
|
106
|
-
"- Assign every file to exactly ONE task (no file may appear in multiple tasks)",
|
|
107
|
-
"- Files that import each other MUST be in the same task",
|
|
108
|
-
"- A module and its test file MUST be in the same task",
|
|
109
|
-
"- Each task should own 2-8 files. Split or merge if outside this range",
|
|
110
|
-
"- Order tasks into stages: infrastructure → core logic → integration → validation",
|
|
111
|
-
"",
|
|
112
|
-
"### 4. Acceptance Criteria",
|
|
113
|
-
"- Every task MUST have machine-verifiable acceptance criteria",
|
|
114
|
-
"- Valid: 'node --check src/foo.mjs passes', 'npm test -- --grep auth passes', 'function X is exported from Y'",
|
|
115
|
-
"- Invalid: 'code is clean', 'implementation is correct', 'works as expected'",
|
|
116
|
-
"- The FINAL task must include: 'all modified files parse without errors AND project builds AND tests pass'",
|
|
117
|
-
"",
|
|
118
|
-
"### 5. Edge Cases & Error Handling Strategy",
|
|
119
|
-
"- List edge cases for each major function (null input, empty arrays, concurrent access, network failure)",
|
|
120
|
-
"- Define the error propagation strategy: throw vs return error vs log-and-continue",
|
|
121
|
-
"- Specify retry/fallback behavior for external dependencies",
|
|
122
|
-
"- Define resource cleanup requirements (file handles, timers, connections)"
|
|
123
|
-
)
|
|
124
|
-
} else if (stage === LONGAGENT_4STAGE_STAGES.CODING) {
|
|
125
|
-
parts.push(
|
|
126
|
-
"### 1. Implementation Discipline",
|
|
127
|
-
"- Follow the blueprint from Stage 2 EXACTLY — do not deviate from the agreed architecture",
|
|
128
|
-
"- Read existing files BEFORE modifying them — never edit blind",
|
|
129
|
-
"- When modifying a function, grep for all callers to ensure you update call sites",
|
|
130
|
-
"- When adding imports, verify the target module exists and exports the symbol",
|
|
131
|
-
"",
|
|
132
|
-
"### 2. Code Quality Standards",
|
|
133
|
-
"- Match the project's existing code style (indentation, naming, async patterns, error handling)",
|
|
134
|
-
"- Add error handling at system boundaries (user input, external APIs, file I/O, network calls)",
|
|
135
|
-
"- Do NOT add unnecessary abstractions, wrappers, or 'just in case' code",
|
|
136
|
-
"- Do NOT add comments that restate the code — only comment non-obvious logic",
|
|
137
|
-
"- Ensure all resources are properly cleaned up (timers cleared, listeners removed, handles closed)",
|
|
138
|
-
"",
|
|
139
|
-
"### 3. Testing Requirements",
|
|
140
|
-
"- If the blueprint includes test files, implement them with concrete assertions (not placeholder TODOs)",
|
|
141
|
-
"- Tests must cover: happy path, error cases, edge cases, boundary conditions",
|
|
142
|
-
"- Run `node --check` (or equivalent) on every file you create or modify",
|
|
143
|
-
"- If a test framework exists, run the relevant test suite to verify no regressions",
|
|
144
|
-
"",
|
|
145
|
-
"### 4. Integration Verification",
|
|
146
|
-
"- After implementing, verify imports resolve correctly across all modified files",
|
|
147
|
-
"- Check that exported APIs match the signatures defined in the blueprint",
|
|
148
|
-
"- If modifying shared modules, verify all downstream consumers still work",
|
|
149
|
-
"",
|
|
150
|
-
"### 5. Progress Reporting",
|
|
151
|
-
"- After completing each logical unit, briefly state what was done and what remains",
|
|
152
|
-
"- If you encounter a blocker not covered by the blueprint, document it clearly",
|
|
153
|
-
"- If you discover the blueprint has an error, fix it and note the deviation"
|
|
154
|
-
)
|
|
155
|
-
} else if (stage === LONGAGENT_4STAGE_STAGES.DEBUGGING) {
|
|
156
|
-
parts.push(
|
|
157
|
-
"### 1. Systematic Verification Protocol",
|
|
158
|
-
"- Run syntax checks on ALL modified/created files (node --check, python -m py_compile, etc.)",
|
|
159
|
-
"- Run the full test suite — not just new tests, ALL tests to catch regressions",
|
|
160
|
-
"- If build system exists (npm run build, make, cargo build), run it and verify success",
|
|
161
|
-
"- Check for TypeScript type errors if tsconfig.json exists (npx tsc --noEmit)",
|
|
162
|
-
"",
|
|
163
|
-
"### 2. Functional Validation",
|
|
164
|
-
"- Trace through each sub-requirement from the blueprint and verify it is implemented",
|
|
165
|
-
"- For each public API: verify the function exists, has correct signature, handles edge cases",
|
|
166
|
-
"- Test error paths: pass invalid input, simulate failures, verify error messages are helpful",
|
|
167
|
-
"- Verify resource cleanup: no timer leaks, no unclosed handles, no dangling event listeners",
|
|
168
|
-
"",
|
|
169
|
-
"### 3. Integration Testing",
|
|
170
|
-
"- Verify cross-module imports resolve correctly",
|
|
171
|
-
"- If the implementation involves multiple stages, verify the data flow end-to-end",
|
|
172
|
-
"- Check for race conditions in async code (concurrent access, Promise.all error handling)",
|
|
173
|
-
"- Verify backward compatibility: existing callers of modified APIs still work",
|
|
174
|
-
"",
|
|
175
|
-
"### 4. Issue Resolution",
|
|
176
|
-
"- For each failing test: read the error, identify root cause, fix it, re-run to confirm",
|
|
177
|
-
"- Do NOT suppress errors or skip tests — fix the underlying issue",
|
|
178
|
-
"- If a fix requires architectural changes, output [RETURN TO STAGE 3: CODING] with details",
|
|
179
|
-
"- Track all issues found and their resolutions",
|
|
180
|
-
"",
|
|
181
|
-
"### 5. Completion Report",
|
|
182
|
-
"When ALL checks pass, provide:",
|
|
183
|
-
"- **Summary**: what was implemented (1-3 sentences)",
|
|
184
|
-
"- **Files changed**: list of created/modified files",
|
|
185
|
-
"- **How to verify**: exact commands to run (build, test, lint)",
|
|
186
|
-
"- **Usage**: how to use the new feature (API examples, CLI commands, config)",
|
|
187
|
-
"- **Known limitations**: anything not covered or deferred"
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
parts.push("", "## STAGE COMPLETION", `When you have completed this stage, end your response with:`, "```", `[STAGE ${info.num}: ${info.name} - COMPLETE]`, "```")
|
|
192
|
-
|
|
193
|
-
if (context.preview && stage !== LONGAGENT_4STAGE_STAGES.PREVIEW) {
|
|
194
|
-
parts.push("", "=== PREVIEW STAGE CONTEXT ===", context.preview)
|
|
195
|
-
}
|
|
196
|
-
if (context.blueprint && (stage === LONGAGENT_4STAGE_STAGES.CODING || stage === LONGAGENT_4STAGE_STAGES.DEBUGGING)) {
|
|
197
|
-
parts.push("", "=== BLUEPRINT STAGE CONTEXT ===", context.blueprint)
|
|
198
|
-
}
|
|
199
|
-
if (context.coding && stage === LONGAGENT_4STAGE_STAGES.DEBUGGING) {
|
|
200
|
-
parts.push("", "=== CODING STAGE OUTPUT ===", context.coding)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (warningMsg) {
|
|
204
|
-
parts.push("", "=== WARNING ===", warningMsg, "")
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
parts.push("", "=== USER OBJECTIVE ===", userPrompt)
|
|
208
|
-
return parts.join("\n")
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export async function run4StageLongAgent({
|
|
212
|
-
prompt, model, providerType, sessionId, configState,
|
|
213
|
-
baseUrl = null, apiKeyEnv = null, agent = null, signal = null,
|
|
214
|
-
output = null, allowQuestion = true, toolContext = {}
|
|
215
|
-
}) {
|
|
216
|
-
const longagentConfig = configState.config.agent.longagent || {}
|
|
217
|
-
const fourStageConfig = longagentConfig.four_stage || {}
|
|
218
|
-
const gitConfig = longagentConfig.git || {}
|
|
219
|
-
const gitEnabled = gitConfig.enabled === true || gitConfig.enabled === "ask"
|
|
220
|
-
const gitAsk = gitConfig.enabled === "ask"
|
|
221
|
-
|
|
222
|
-
let iteration = 0
|
|
223
|
-
let currentStage = LONGAGENT_4STAGE_STAGES.PREVIEW
|
|
224
|
-
let currentStageIteration = 0
|
|
225
|
-
const stageContext = { preview: null, blueprint: null, coding: null }
|
|
226
|
-
let finalReply = ""
|
|
227
|
-
const aggregateUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
228
|
-
const toolEvents = []
|
|
229
|
-
const startTime = Date.now()
|
|
230
|
-
let completionMarkerSeen = false
|
|
231
|
-
let gitBranch = null, gitBaseBranch = null, gitActive = false
|
|
232
|
-
let stuckWarningMsg = null
|
|
233
|
-
const stuckTracker = createStuckTracker()
|
|
234
|
-
|
|
235
|
-
const stageMaxIterations = {
|
|
236
|
-
[LONGAGENT_4STAGE_STAGES.PREVIEW]: Number(fourStageConfig.preview_max_iterations || 10),
|
|
237
|
-
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: Number(fourStageConfig.blueprint_max_iterations || 10),
|
|
238
|
-
[LONGAGENT_4STAGE_STAGES.CODING]: Number(fourStageConfig.coding_max_iterations || 50),
|
|
239
|
-
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: Number(fourStageConfig.debugging_max_iterations || 20)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const separateModels = fourStageConfig.separate_models || {}
|
|
243
|
-
const useSeparateModels = separateModels.enabled === true
|
|
244
|
-
|
|
245
|
-
function getModelForStage(stage) {
|
|
246
|
-
if (!useSeparateModels) return { model, providerType }
|
|
247
|
-
const map = {
|
|
248
|
-
[LONGAGENT_4STAGE_STAGES.PREVIEW]: separateModels.preview_model,
|
|
249
|
-
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: separateModels.blueprint_model,
|
|
250
|
-
[LONGAGENT_4STAGE_STAGES.CODING]: separateModels.coding_model,
|
|
251
|
-
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: separateModels.debugging_model
|
|
252
|
-
}
|
|
253
|
-
return map[stage] ? { model: map[stage], providerType } : { model, providerType }
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async function setStage(nextStage) {
|
|
257
|
-
if (currentStage === nextStage) return
|
|
258
|
-
currentStage = nextStage
|
|
259
|
-
currentStageIteration = 0
|
|
260
|
-
const eventMap = {
|
|
261
|
-
[LONGAGENT_4STAGE_STAGES.PREVIEW]: EVENT_TYPES.LONGAGENT_4STAGE_PREVIEW_START,
|
|
262
|
-
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: EVENT_TYPES.LONGAGENT_4STAGE_BLUEPRINT_START,
|
|
263
|
-
[LONGAGENT_4STAGE_STAGES.CODING]: EVENT_TYPES.LONGAGENT_4STAGE_CODING_START,
|
|
264
|
-
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: EVENT_TYPES.LONGAGENT_4STAGE_DEBUGGING_START
|
|
265
|
-
}
|
|
266
|
-
if (eventMap[nextStage]) {
|
|
267
|
-
await EventBus.emit({ type: eventMap[nextStage], sessionId, payload: { stage: nextStage, iteration } })
|
|
268
|
-
}
|
|
269
|
-
await syncState({ lastMessage: `entering stage: ${nextStage}` })
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function emitStageComplete(stage) {
|
|
273
|
-
const eventMap = {
|
|
274
|
-
[LONGAGENT_4STAGE_STAGES.PREVIEW]: EVENT_TYPES.LONGAGENT_4STAGE_PREVIEW_COMPLETE,
|
|
275
|
-
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: EVENT_TYPES.LONGAGENT_4STAGE_BLUEPRINT_COMPLETE,
|
|
276
|
-
[LONGAGENT_4STAGE_STAGES.CODING]: EVENT_TYPES.LONGAGENT_4STAGE_CODING_COMPLETE,
|
|
277
|
-
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: EVENT_TYPES.LONGAGENT_4STAGE_DEBUGGING_COMPLETE
|
|
278
|
-
}
|
|
279
|
-
if (eventMap[stage]) {
|
|
280
|
-
await EventBus.emit({ type: eventMap[stage], sessionId, payload: { stage, iteration } })
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async function syncState(patch = {}) {
|
|
285
|
-
await LongAgentManager.update(sessionId, {
|
|
286
|
-
status: patch.status || "running",
|
|
287
|
-
fourStage: { currentStage, stageContext },
|
|
288
|
-
iterations: iteration,
|
|
289
|
-
heartbeatAt: Date.now(),
|
|
290
|
-
...patch
|
|
291
|
-
})
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
await markSessionStatus(sessionId, "running-longagent")
|
|
295
|
-
await syncState({ status: "running", lastMessage: "4-stage longagent started" })
|
|
296
|
-
|
|
297
|
-
// Git branch setup
|
|
298
|
-
const cwd = process.cwd()
|
|
299
|
-
const inGitRepo = gitEnabled && await git.isGitRepo(cwd)
|
|
300
|
-
if (inGitRepo) {
|
|
301
|
-
let userWantsGit = !gitAsk
|
|
302
|
-
if (gitAsk && allowQuestion) {
|
|
303
|
-
const askResult = await processTurnLoop({
|
|
304
|
-
prompt: "[SYSTEM] Git 分支管理已就绪。是否为本次 LongAgent 会话创建独立分支?\n回复 yes/是 启用,no/否 跳过。\n启用后:自动创建特性分支 → 每阶段自动提交 → 完成后合并回主分支。",
|
|
305
|
-
mode: "
|
|
306
|
-
baseUrl, apiKeyEnv, agent, signal, allowQuestion: true, toolContext
|
|
307
|
-
})
|
|
308
|
-
const answer = String(askResult.reply || "").toLowerCase().trim()
|
|
309
|
-
userWantsGit = ["yes", "是", "y", "ok", "好", "确认", "开启", "启用"].some(k => answer.includes(k))
|
|
310
|
-
aggregateUsage.input += askResult.usage.input || 0
|
|
311
|
-
aggregateUsage.output += askResult.usage.output || 0
|
|
312
|
-
}
|
|
313
|
-
if (userWantsGit) {
|
|
314
|
-
gitBaseBranch = await git.currentBranch(cwd)
|
|
315
|
-
const branchName = git.generateBranchName(sessionId, prompt)
|
|
316
|
-
const clean = await git.isClean(cwd)
|
|
317
|
-
let stashed = false
|
|
318
|
-
if (!clean) {
|
|
319
|
-
const stashResult = await git.stash("kkcode-auto-stash-before-branch", cwd)
|
|
320
|
-
stashed = stashResult.ok
|
|
321
|
-
}
|
|
322
|
-
try {
|
|
323
|
-
const created = await git.createBranch(branchName, cwd)
|
|
324
|
-
if (created.ok) { gitBranch = branchName; gitActive = true }
|
|
325
|
-
} finally {
|
|
326
|
-
if (stashed) await git.stashPop(cwd).catch(() => {})
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Main stage loop
|
|
332
|
-
const stageOrder = [
|
|
333
|
-
LONGAGENT_4STAGE_STAGES.PREVIEW,
|
|
334
|
-
LONGAGENT_4STAGE_STAGES.BLUEPRINT,
|
|
335
|
-
LONGAGENT_4STAGE_STAGES.CODING,
|
|
336
|
-
LONGAGENT_4STAGE_STAGES.DEBUGGING
|
|
337
|
-
]
|
|
338
|
-
let stageIndex = 0
|
|
339
|
-
let codingRollbackCount = 0
|
|
340
|
-
const maxCodingRollbacks = Number(fourStageConfig.max_coding_rollbacks || 3)
|
|
341
|
-
|
|
342
|
-
while (stageIndex < stageOrder.length) {
|
|
343
|
-
const stage = stageOrder[stageIndex]
|
|
344
|
-
await setStage(stage)
|
|
345
|
-
const { model: stageModel, providerType: stageProvider } = getModelForStage(stage)
|
|
346
|
-
const maxIter = stageMaxIterations[stage]
|
|
347
|
-
let stageComplete = false
|
|
348
|
-
|
|
349
|
-
while (!stageComplete && currentStageIteration < maxIter) {
|
|
350
|
-
iteration += 1
|
|
351
|
-
currentStageIteration += 1
|
|
352
|
-
|
|
353
|
-
const state = await LongAgentManager.get(sessionId)
|
|
354
|
-
if (state?.stopRequested || signal?.aborted) {
|
|
355
|
-
await LongAgentManager.update(sessionId, { status: "stopped", lastMessage: "stop requested" })
|
|
356
|
-
await markSessionStatus(sessionId, "stopped")
|
|
357
|
-
return { sessionId, reply: "longagent stopped", usage: aggregateUsage, toolEvents, iterations: iteration, status: "stopped" }
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const fullPrompt = buildStageWrapper(stage, stageContext, prompt, stuckWarningMsg)
|
|
361
|
-
if (stuckWarningMsg) stuckWarningMsg = null
|
|
362
|
-
const readonly = stage === LONGAGENT_4STAGE_STAGES.PREVIEW || stage === LONGAGENT_4STAGE_STAGES.BLUEPRINT
|
|
363
|
-
const stageAgentName = readonly
|
|
364
|
-
? (stage === LONGAGENT_4STAGE_STAGES.PREVIEW ? "preview-agent" : "blueprint-agent")
|
|
365
|
-
: (stage === LONGAGENT_4STAGE_STAGES.CODING ? "coding-agent" : "debugging-agent")
|
|
366
|
-
|
|
367
|
-
const out = await processTurnLoop({
|
|
368
|
-
prompt: fullPrompt, mode: "agent", agent: getAgent(stageAgentName),
|
|
369
|
-
model: stageModel, providerType: stageProvider, sessionId, configState,
|
|
370
|
-
baseUrl, apiKeyEnv, signal, output, allowQuestion, toolContext
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
aggregateUsage.input += out.usage.input || 0
|
|
374
|
-
aggregateUsage.output += out.usage.output || 0
|
|
375
|
-
if (out.toolEvents?.length) toolEvents.push(...out.toolEvents)
|
|
376
|
-
finalReply = out.reply
|
|
377
|
-
|
|
378
|
-
// 防卡死检测
|
|
379
|
-
if (out.toolEvents?.length) {
|
|
380
|
-
const stuckResult = stuckTracker.track(out.toolEvents)
|
|
381
|
-
if (stuckResult.isStuck) {
|
|
382
|
-
const readonly = stage === LONGAGENT_4STAGE_STAGES.PREVIEW || stage === LONGAGENT_4STAGE_STAGES.BLUEPRINT
|
|
383
|
-
stuckWarningMsg = readonly
|
|
384
|
-
? `[STUCK DETECTION] You have been exploring files for too many rounds without progress. STOP reading more files. Synthesize what you've learned and COMPLETE this stage now.`
|
|
385
|
-
: `[STUCK DETECTION] You appear stuck in an exploration loop. STOP reading files and START implementing. Make concrete changes to files.`
|
|
386
|
-
stuckTracker.resetReadOnlyCount()
|
|
387
|
-
await EventBus.emit({
|
|
388
|
-
type: EVENT_TYPES.LONGAGENT_ALERT, sessionId,
|
|
389
|
-
payload: { kind: "stuck_warning", stage, reason: stuckResult.reason, iteration: currentStageIteration }
|
|
390
|
-
})
|
|
391
|
-
await syncState({ lastMessage: `stuck detected at ${stage}, iter ${currentStageIteration}` })
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (detectStageComplete(out.reply, stage)) {
|
|
396
|
-
stageComplete = true
|
|
397
|
-
stageContext[stage] = out.reply
|
|
398
|
-
await emitStageComplete(stage)
|
|
399
|
-
if (gitActive && gitConfig.auto_commit_stages !== false) {
|
|
400
|
-
await git.commitAll(`[kkcode] 4-stage: ${stage} completed`, cwd).catch(() => {})
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Debugging → Coding回退(带次数限制)
|
|
405
|
-
if (stage === LONGAGENT_4STAGE_STAGES.DEBUGGING && detectReturnToCoding(out.reply)) {
|
|
406
|
-
codingRollbackCount++
|
|
407
|
-
if (codingRollbackCount > maxCodingRollbacks) {
|
|
408
|
-
await EventBus.emit({ type: EVENT_TYPES.LONGAGENT_ALERT, sessionId, payload: { kind: "rollback_limit", message: `coding rollback limit (${maxCodingRollbacks}) reached, forcing completion` } })
|
|
409
|
-
stageComplete = true
|
|
410
|
-
continue
|
|
411
|
-
}
|
|
412
|
-
await EventBus.emit({ type: EVENT_TYPES.LONGAGENT_4STAGE_RETURN_TO_CODING, sessionId, payload: { rollbackCount: codingRollbackCount } })
|
|
413
|
-
stageIndex = stageOrder.indexOf(LONGAGENT_4STAGE_STAGES.CODING)
|
|
414
|
-
stageComplete = true
|
|
415
|
-
continue
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (/\[TASK_COMPLETE\]/i.test(out.reply)) completionMarkerSeen = true
|
|
419
|
-
await syncState({ lastMessage: `stage ${stage}, iteration ${currentStageIteration}/${maxIter}` })
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
if (!stageComplete) {
|
|
423
|
-
await LongAgentManager.update(sessionId, { status: "failed", lastMessage: `stage ${stage} timed out after ${maxIter} iterations` })
|
|
424
|
-
return { sessionId, reply: `stage ${stage} timed out`, usage: aggregateUsage, toolEvents, iterations: iteration, status: "failed" }
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
stageIndex += 1
|
|
428
|
-
await saveCheckpoint(sessionId, { name: `4stage_${stage}`, iteration, currentStage, stageContext })
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Git merge (only if not failed)
|
|
432
|
-
if (gitActive && gitBaseBranch && gitBranch) {
|
|
433
|
-
try {
|
|
434
|
-
await git.commitAll(`[kkcode] 4-stage session ${sessionId} completed`, cwd)
|
|
435
|
-
if (gitConfig.auto_merge !== false) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1
|
+
import { LongAgentManager } from "../orchestration/longagent-manager.mjs"
|
|
2
|
+
import { processTurnLoop } from "./loop.mjs"
|
|
3
|
+
import { markSessionStatus } from "./store.mjs"
|
|
4
|
+
import { EventBus } from "../core/events.mjs"
|
|
5
|
+
import {
|
|
6
|
+
EVENT_TYPES,
|
|
7
|
+
LONGAGENT_4STAGE_STAGES
|
|
8
|
+
} from "../core/constants.mjs"
|
|
9
|
+
import { saveCheckpoint } from "./checkpoint.mjs"
|
|
10
|
+
import { getAgent } from "../agent/agent.mjs"
|
|
11
|
+
import { createStuckTracker, isReadOnlyTool } from "./longagent-utils.mjs"
|
|
12
|
+
import * as git from "../util/git.mjs"
|
|
13
|
+
|
|
14
|
+
export function detectStageComplete(text, stage) {
|
|
15
|
+
const str = String(text || "")
|
|
16
|
+
const markers = {
|
|
17
|
+
[LONGAGENT_4STAGE_STAGES.PREVIEW]: /\[STAGE 1\/4: PREVIEW(?:ING AGENT)? - COMPLETE\]/,
|
|
18
|
+
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: /\[STAGE 2\/4: BLUEPRINT(?:\s+AGENT)? - COMPLETE\]/,
|
|
19
|
+
[LONGAGENT_4STAGE_STAGES.CODING]: /\[STAGE 3\/4: CODING(?:\s+AGENT)? - COMPLETE\]/,
|
|
20
|
+
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: /\[STAGE 4\/4: DEBUGGING(?:\s+AGENT)? - COMPLETE\]/
|
|
21
|
+
}
|
|
22
|
+
return markers[stage] ? markers[stage].test(str) : false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function detectReturnToCoding(text) {
|
|
26
|
+
return /\[RETURN TO STAGE 3/.test(String(text || ""))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function buildStageWrapper(stage, context, userPrompt, warningMsg = null) {
|
|
30
|
+
const stageInfo = {
|
|
31
|
+
[LONGAGENT_4STAGE_STAGES.PREVIEW]: { num: "1/4", name: "PREVIEW", focus: "Explore project, understand requirements, extract key information", readonly: true },
|
|
32
|
+
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: { num: "2/4", name: "BLUEPRINT", focus: "Detailed planning, architecture design, function definitions", readonly: true },
|
|
33
|
+
[LONGAGENT_4STAGE_STAGES.CODING]: { num: "3/4", name: "CODING", focus: "Implement code strictly according to blueprint", readonly: false },
|
|
34
|
+
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: { num: "4/4", name: "DEBUGGING", focus: "Verify implementation, test, debug, validate completion", readonly: false }
|
|
35
|
+
}
|
|
36
|
+
const info = stageInfo[stage]
|
|
37
|
+
const parts = [
|
|
38
|
+
`=== LONGAGENT STAGE ${info.num}: ${info.name} ===`,
|
|
39
|
+
"",
|
|
40
|
+
`# STAGE OBJECTIVE: ${info.focus}`,
|
|
41
|
+
"",
|
|
42
|
+
`IMPORTANT: You are in STAGE ${info.num} of the four-stage LongAgent workflow.`,
|
|
43
|
+
""
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
if (info.readonly) {
|
|
47
|
+
parts.push(
|
|
48
|
+
"## PERMISSION CONSTRAINTS",
|
|
49
|
+
"YOU ARE IN READ-ONLY MODE FOR THIS STAGE.",
|
|
50
|
+
"- You MAY use: read, glob, grep, list, bash, question, todowrite",
|
|
51
|
+
"- You MUST NOT use: write, edit, patch, or any file modification tools",
|
|
52
|
+
""
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
parts.push("## YOUR TASKS FOR THIS STAGE:")
|
|
57
|
+
if (stage === LONGAGENT_4STAGE_STAGES.PREVIEW) {
|
|
58
|
+
parts.push(
|
|
59
|
+
"### 1. Project Structure Discovery",
|
|
60
|
+
"- Use glob to map the FULL directory tree (src/, test/, config/, scripts/, etc.)",
|
|
61
|
+
"- Identify the build system (package.json scripts, Makefile, Cargo.toml, etc.)",
|
|
62
|
+
"- Identify the test framework and test file naming convention",
|
|
63
|
+
"- Read the entry point(s) and trace the module dependency graph",
|
|
64
|
+
"",
|
|
65
|
+
"### 2. Technology Stack Audit",
|
|
66
|
+
"- Read package.json / requirements.txt / go.mod to catalog ALL dependencies",
|
|
67
|
+
"- Identify the runtime version constraints (engines, python_requires, etc.)",
|
|
68
|
+
"- Note the code style: ESM vs CJS, TypeScript vs JS, async patterns, error handling conventions",
|
|
69
|
+
"- Check for existing linter/formatter config (.eslintrc, .prettierrc, pyproject.toml)",
|
|
70
|
+
"",
|
|
71
|
+
"### 3. Requirement Decomposition",
|
|
72
|
+
"- Break the user objective into discrete, testable sub-requirements",
|
|
73
|
+
"- For each sub-requirement, identify which existing modules are affected",
|
|
74
|
+
"- Flag any ambiguities or missing information that could cause parallel agents to conflict",
|
|
75
|
+
"- Identify external API contracts, data schemas, or protocols involved",
|
|
76
|
+
"",
|
|
77
|
+
"### 4. Reuse & Risk Assessment",
|
|
78
|
+
"- List existing utilities, helpers, and abstractions that MUST be reused (do NOT reinvent)",
|
|
79
|
+
"- Identify files that are heavily imported — changes to these have high blast radius",
|
|
80
|
+
"- Note any existing tests that cover the affected modules (these must not regress)",
|
|
81
|
+
"- Flag potential conflicts: concurrent file access, circular dependencies, breaking API changes",
|
|
82
|
+
"",
|
|
83
|
+
"### 5. Output Format",
|
|
84
|
+
"Produce a structured findings report with these sections:",
|
|
85
|
+
"- **Tech Stack**: runtime, framework, key dependencies, build tool",
|
|
86
|
+
"- **Affected Modules**: list of files/directories that will be touched",
|
|
87
|
+
"- **Reusable Assets**: existing code to leverage",
|
|
88
|
+
"- **Risks**: potential issues, breaking changes, high-blast-radius files",
|
|
89
|
+
"- **Sub-requirements**: numbered list of discrete tasks derived from the objective"
|
|
90
|
+
)
|
|
91
|
+
} else if (stage === LONGAGENT_4STAGE_STAGES.BLUEPRINT) {
|
|
92
|
+
parts.push(
|
|
93
|
+
"### 1. Architecture Design",
|
|
94
|
+
"- Define the module boundaries: which new files to create, which existing files to modify",
|
|
95
|
+
"- For each new module: purpose, public API (exported functions/classes with signatures), internal structure",
|
|
96
|
+
"- For each modified module: what changes, what stays, backward compatibility impact",
|
|
97
|
+
"- Draw the dependency graph: A imports B, B imports C — ensure no circular dependencies",
|
|
98
|
+
"",
|
|
99
|
+
"### 2. Interface Contracts",
|
|
100
|
+
"- Define ALL function signatures with parameter types and return types",
|
|
101
|
+
"- Define data structures / schemas (object shapes, DB schemas, API request/response formats)",
|
|
102
|
+
"- Specify error types: what errors can each function throw, how callers should handle them",
|
|
103
|
+
"- Define event contracts if using pub/sub or EventEmitter patterns",
|
|
104
|
+
"",
|
|
105
|
+
"### 3. File Ownership & Parallelization Plan",
|
|
106
|
+
"- Assign every file to exactly ONE task (no file may appear in multiple tasks)",
|
|
107
|
+
"- Files that import each other MUST be in the same task",
|
|
108
|
+
"- A module and its test file MUST be in the same task",
|
|
109
|
+
"- Each task should own 2-8 files. Split or merge if outside this range",
|
|
110
|
+
"- Order tasks into stages: infrastructure → core logic → integration → validation",
|
|
111
|
+
"",
|
|
112
|
+
"### 4. Acceptance Criteria",
|
|
113
|
+
"- Every task MUST have machine-verifiable acceptance criteria",
|
|
114
|
+
"- Valid: 'node --check src/foo.mjs passes', 'npm test -- --grep auth passes', 'function X is exported from Y'",
|
|
115
|
+
"- Invalid: 'code is clean', 'implementation is correct', 'works as expected'",
|
|
116
|
+
"- The FINAL task must include: 'all modified files parse without errors AND project builds AND tests pass'",
|
|
117
|
+
"",
|
|
118
|
+
"### 5. Edge Cases & Error Handling Strategy",
|
|
119
|
+
"- List edge cases for each major function (null input, empty arrays, concurrent access, network failure)",
|
|
120
|
+
"- Define the error propagation strategy: throw vs return error vs log-and-continue",
|
|
121
|
+
"- Specify retry/fallback behavior for external dependencies",
|
|
122
|
+
"- Define resource cleanup requirements (file handles, timers, connections)"
|
|
123
|
+
)
|
|
124
|
+
} else if (stage === LONGAGENT_4STAGE_STAGES.CODING) {
|
|
125
|
+
parts.push(
|
|
126
|
+
"### 1. Implementation Discipline",
|
|
127
|
+
"- Follow the blueprint from Stage 2 EXACTLY — do not deviate from the agreed architecture",
|
|
128
|
+
"- Read existing files BEFORE modifying them — never edit blind",
|
|
129
|
+
"- When modifying a function, grep for all callers to ensure you update call sites",
|
|
130
|
+
"- When adding imports, verify the target module exists and exports the symbol",
|
|
131
|
+
"",
|
|
132
|
+
"### 2. Code Quality Standards",
|
|
133
|
+
"- Match the project's existing code style (indentation, naming, async patterns, error handling)",
|
|
134
|
+
"- Add error handling at system boundaries (user input, external APIs, file I/O, network calls)",
|
|
135
|
+
"- Do NOT add unnecessary abstractions, wrappers, or 'just in case' code",
|
|
136
|
+
"- Do NOT add comments that restate the code — only comment non-obvious logic",
|
|
137
|
+
"- Ensure all resources are properly cleaned up (timers cleared, listeners removed, handles closed)",
|
|
138
|
+
"",
|
|
139
|
+
"### 3. Testing Requirements",
|
|
140
|
+
"- If the blueprint includes test files, implement them with concrete assertions (not placeholder TODOs)",
|
|
141
|
+
"- Tests must cover: happy path, error cases, edge cases, boundary conditions",
|
|
142
|
+
"- Run `node --check` (or equivalent) on every file you create or modify",
|
|
143
|
+
"- If a test framework exists, run the relevant test suite to verify no regressions",
|
|
144
|
+
"",
|
|
145
|
+
"### 4. Integration Verification",
|
|
146
|
+
"- After implementing, verify imports resolve correctly across all modified files",
|
|
147
|
+
"- Check that exported APIs match the signatures defined in the blueprint",
|
|
148
|
+
"- If modifying shared modules, verify all downstream consumers still work",
|
|
149
|
+
"",
|
|
150
|
+
"### 5. Progress Reporting",
|
|
151
|
+
"- After completing each logical unit, briefly state what was done and what remains",
|
|
152
|
+
"- If you encounter a blocker not covered by the blueprint, document it clearly",
|
|
153
|
+
"- If you discover the blueprint has an error, fix it and note the deviation"
|
|
154
|
+
)
|
|
155
|
+
} else if (stage === LONGAGENT_4STAGE_STAGES.DEBUGGING) {
|
|
156
|
+
parts.push(
|
|
157
|
+
"### 1. Systematic Verification Protocol",
|
|
158
|
+
"- Run syntax checks on ALL modified/created files (node --check, python -m py_compile, etc.)",
|
|
159
|
+
"- Run the full test suite — not just new tests, ALL tests to catch regressions",
|
|
160
|
+
"- If build system exists (npm run build, make, cargo build), run it and verify success",
|
|
161
|
+
"- Check for TypeScript type errors if tsconfig.json exists (npx tsc --noEmit)",
|
|
162
|
+
"",
|
|
163
|
+
"### 2. Functional Validation",
|
|
164
|
+
"- Trace through each sub-requirement from the blueprint and verify it is implemented",
|
|
165
|
+
"- For each public API: verify the function exists, has correct signature, handles edge cases",
|
|
166
|
+
"- Test error paths: pass invalid input, simulate failures, verify error messages are helpful",
|
|
167
|
+
"- Verify resource cleanup: no timer leaks, no unclosed handles, no dangling event listeners",
|
|
168
|
+
"",
|
|
169
|
+
"### 3. Integration Testing",
|
|
170
|
+
"- Verify cross-module imports resolve correctly",
|
|
171
|
+
"- If the implementation involves multiple stages, verify the data flow end-to-end",
|
|
172
|
+
"- Check for race conditions in async code (concurrent access, Promise.all error handling)",
|
|
173
|
+
"- Verify backward compatibility: existing callers of modified APIs still work",
|
|
174
|
+
"",
|
|
175
|
+
"### 4. Issue Resolution",
|
|
176
|
+
"- For each failing test: read the error, identify root cause, fix it, re-run to confirm",
|
|
177
|
+
"- Do NOT suppress errors or skip tests — fix the underlying issue",
|
|
178
|
+
"- If a fix requires architectural changes, output [RETURN TO STAGE 3: CODING] with details",
|
|
179
|
+
"- Track all issues found and their resolutions",
|
|
180
|
+
"",
|
|
181
|
+
"### 5. Completion Report",
|
|
182
|
+
"When ALL checks pass, provide:",
|
|
183
|
+
"- **Summary**: what was implemented (1-3 sentences)",
|
|
184
|
+
"- **Files changed**: list of created/modified files",
|
|
185
|
+
"- **How to verify**: exact commands to run (build, test, lint)",
|
|
186
|
+
"- **Usage**: how to use the new feature (API examples, CLI commands, config)",
|
|
187
|
+
"- **Known limitations**: anything not covered or deferred"
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
parts.push("", "## STAGE COMPLETION", `When you have completed this stage, end your response with:`, "```", `[STAGE ${info.num}: ${info.name} - COMPLETE]`, "```")
|
|
192
|
+
|
|
193
|
+
if (context.preview && stage !== LONGAGENT_4STAGE_STAGES.PREVIEW) {
|
|
194
|
+
parts.push("", "=== PREVIEW STAGE CONTEXT ===", context.preview)
|
|
195
|
+
}
|
|
196
|
+
if (context.blueprint && (stage === LONGAGENT_4STAGE_STAGES.CODING || stage === LONGAGENT_4STAGE_STAGES.DEBUGGING)) {
|
|
197
|
+
parts.push("", "=== BLUEPRINT STAGE CONTEXT ===", context.blueprint)
|
|
198
|
+
}
|
|
199
|
+
if (context.coding && stage === LONGAGENT_4STAGE_STAGES.DEBUGGING) {
|
|
200
|
+
parts.push("", "=== CODING STAGE OUTPUT ===", context.coding)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (warningMsg) {
|
|
204
|
+
parts.push("", "=== WARNING ===", warningMsg, "")
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
parts.push("", "=== USER OBJECTIVE ===", userPrompt)
|
|
208
|
+
return parts.join("\n")
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function run4StageLongAgent({
|
|
212
|
+
prompt, model, providerType, sessionId, configState,
|
|
213
|
+
baseUrl = null, apiKeyEnv = null, agent = null, signal = null,
|
|
214
|
+
output = null, allowQuestion = true, toolContext = {}
|
|
215
|
+
}) {
|
|
216
|
+
const longagentConfig = configState.config.agent.longagent || {}
|
|
217
|
+
const fourStageConfig = longagentConfig.four_stage || {}
|
|
218
|
+
const gitConfig = longagentConfig.git || {}
|
|
219
|
+
const gitEnabled = gitConfig.enabled === true || gitConfig.enabled === "ask"
|
|
220
|
+
const gitAsk = gitConfig.enabled === "ask"
|
|
221
|
+
|
|
222
|
+
let iteration = 0
|
|
223
|
+
let currentStage = LONGAGENT_4STAGE_STAGES.PREVIEW
|
|
224
|
+
let currentStageIteration = 0
|
|
225
|
+
const stageContext = { preview: null, blueprint: null, coding: null }
|
|
226
|
+
let finalReply = ""
|
|
227
|
+
const aggregateUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
228
|
+
const toolEvents = []
|
|
229
|
+
const startTime = Date.now()
|
|
230
|
+
let completionMarkerSeen = false
|
|
231
|
+
let gitBranch = null, gitBaseBranch = null, gitActive = false
|
|
232
|
+
let stuckWarningMsg = null
|
|
233
|
+
const stuckTracker = createStuckTracker()
|
|
234
|
+
|
|
235
|
+
const stageMaxIterations = {
|
|
236
|
+
[LONGAGENT_4STAGE_STAGES.PREVIEW]: Number(fourStageConfig.preview_max_iterations || 10),
|
|
237
|
+
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: Number(fourStageConfig.blueprint_max_iterations || 10),
|
|
238
|
+
[LONGAGENT_4STAGE_STAGES.CODING]: Number(fourStageConfig.coding_max_iterations || 50),
|
|
239
|
+
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: Number(fourStageConfig.debugging_max_iterations || 20)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const separateModels = fourStageConfig.separate_models || {}
|
|
243
|
+
const useSeparateModels = separateModels.enabled === true
|
|
244
|
+
|
|
245
|
+
function getModelForStage(stage) {
|
|
246
|
+
if (!useSeparateModels) return { model, providerType }
|
|
247
|
+
const map = {
|
|
248
|
+
[LONGAGENT_4STAGE_STAGES.PREVIEW]: separateModels.preview_model,
|
|
249
|
+
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: separateModels.blueprint_model,
|
|
250
|
+
[LONGAGENT_4STAGE_STAGES.CODING]: separateModels.coding_model,
|
|
251
|
+
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: separateModels.debugging_model
|
|
252
|
+
}
|
|
253
|
+
return map[stage] ? { model: map[stage], providerType } : { model, providerType }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function setStage(nextStage) {
|
|
257
|
+
if (currentStage === nextStage) return
|
|
258
|
+
currentStage = nextStage
|
|
259
|
+
currentStageIteration = 0
|
|
260
|
+
const eventMap = {
|
|
261
|
+
[LONGAGENT_4STAGE_STAGES.PREVIEW]: EVENT_TYPES.LONGAGENT_4STAGE_PREVIEW_START,
|
|
262
|
+
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: EVENT_TYPES.LONGAGENT_4STAGE_BLUEPRINT_START,
|
|
263
|
+
[LONGAGENT_4STAGE_STAGES.CODING]: EVENT_TYPES.LONGAGENT_4STAGE_CODING_START,
|
|
264
|
+
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: EVENT_TYPES.LONGAGENT_4STAGE_DEBUGGING_START
|
|
265
|
+
}
|
|
266
|
+
if (eventMap[nextStage]) {
|
|
267
|
+
await EventBus.emit({ type: eventMap[nextStage], sessionId, payload: { stage: nextStage, iteration } })
|
|
268
|
+
}
|
|
269
|
+
await syncState({ lastMessage: `entering stage: ${nextStage}` })
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function emitStageComplete(stage) {
|
|
273
|
+
const eventMap = {
|
|
274
|
+
[LONGAGENT_4STAGE_STAGES.PREVIEW]: EVENT_TYPES.LONGAGENT_4STAGE_PREVIEW_COMPLETE,
|
|
275
|
+
[LONGAGENT_4STAGE_STAGES.BLUEPRINT]: EVENT_TYPES.LONGAGENT_4STAGE_BLUEPRINT_COMPLETE,
|
|
276
|
+
[LONGAGENT_4STAGE_STAGES.CODING]: EVENT_TYPES.LONGAGENT_4STAGE_CODING_COMPLETE,
|
|
277
|
+
[LONGAGENT_4STAGE_STAGES.DEBUGGING]: EVENT_TYPES.LONGAGENT_4STAGE_DEBUGGING_COMPLETE
|
|
278
|
+
}
|
|
279
|
+
if (eventMap[stage]) {
|
|
280
|
+
await EventBus.emit({ type: eventMap[stage], sessionId, payload: { stage, iteration } })
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function syncState(patch = {}) {
|
|
285
|
+
await LongAgentManager.update(sessionId, {
|
|
286
|
+
status: patch.status || "running",
|
|
287
|
+
fourStage: { currentStage, stageContext },
|
|
288
|
+
iterations: iteration,
|
|
289
|
+
heartbeatAt: Date.now(),
|
|
290
|
+
...patch
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
await markSessionStatus(sessionId, "running-longagent")
|
|
295
|
+
await syncState({ status: "running", lastMessage: "4-stage longagent started" })
|
|
296
|
+
|
|
297
|
+
// Git branch setup
|
|
298
|
+
const cwd = process.cwd()
|
|
299
|
+
const inGitRepo = gitEnabled && await git.isGitRepo(cwd)
|
|
300
|
+
if (inGitRepo) {
|
|
301
|
+
let userWantsGit = !gitAsk
|
|
302
|
+
if (gitAsk && allowQuestion) {
|
|
303
|
+
const askResult = await processTurnLoop({
|
|
304
|
+
prompt: "[SYSTEM] Git 分支管理已就绪。是否为本次 LongAgent 会话创建独立分支?\n回复 yes/是 启用,no/否 跳过。\n启用后:自动创建特性分支 → 每阶段自动提交 → 完成后合并回主分支。",
|
|
305
|
+
mode: "assistant", model, providerType, sessionId, configState,
|
|
306
|
+
baseUrl, apiKeyEnv, agent, signal, allowQuestion: true, toolContext
|
|
307
|
+
})
|
|
308
|
+
const answer = String(askResult.reply || "").toLowerCase().trim()
|
|
309
|
+
userWantsGit = ["yes", "是", "y", "ok", "好", "确认", "开启", "启用"].some(k => answer.includes(k))
|
|
310
|
+
aggregateUsage.input += askResult.usage.input || 0
|
|
311
|
+
aggregateUsage.output += askResult.usage.output || 0
|
|
312
|
+
}
|
|
313
|
+
if (userWantsGit) {
|
|
314
|
+
gitBaseBranch = await git.currentBranch(cwd)
|
|
315
|
+
const branchName = git.generateBranchName(sessionId, prompt)
|
|
316
|
+
const clean = await git.isClean(cwd)
|
|
317
|
+
let stashed = false
|
|
318
|
+
if (!clean) {
|
|
319
|
+
const stashResult = await git.stash("kkcode-auto-stash-before-branch", cwd)
|
|
320
|
+
stashed = stashResult.ok
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const created = await git.createBranch(branchName, cwd)
|
|
324
|
+
if (created.ok) { gitBranch = branchName; gitActive = true }
|
|
325
|
+
} finally {
|
|
326
|
+
if (stashed) await git.stashPop(cwd).catch(() => {})
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Main stage loop
|
|
332
|
+
const stageOrder = [
|
|
333
|
+
LONGAGENT_4STAGE_STAGES.PREVIEW,
|
|
334
|
+
LONGAGENT_4STAGE_STAGES.BLUEPRINT,
|
|
335
|
+
LONGAGENT_4STAGE_STAGES.CODING,
|
|
336
|
+
LONGAGENT_4STAGE_STAGES.DEBUGGING
|
|
337
|
+
]
|
|
338
|
+
let stageIndex = 0
|
|
339
|
+
let codingRollbackCount = 0
|
|
340
|
+
const maxCodingRollbacks = Number(fourStageConfig.max_coding_rollbacks || 3)
|
|
341
|
+
|
|
342
|
+
while (stageIndex < stageOrder.length) {
|
|
343
|
+
const stage = stageOrder[stageIndex]
|
|
344
|
+
await setStage(stage)
|
|
345
|
+
const { model: stageModel, providerType: stageProvider } = getModelForStage(stage)
|
|
346
|
+
const maxIter = stageMaxIterations[stage]
|
|
347
|
+
let stageComplete = false
|
|
348
|
+
|
|
349
|
+
while (!stageComplete && currentStageIteration < maxIter) {
|
|
350
|
+
iteration += 1
|
|
351
|
+
currentStageIteration += 1
|
|
352
|
+
|
|
353
|
+
const state = await LongAgentManager.get(sessionId)
|
|
354
|
+
if (state?.stopRequested || signal?.aborted) {
|
|
355
|
+
await LongAgentManager.update(sessionId, { status: "stopped", lastMessage: "stop requested" })
|
|
356
|
+
await markSessionStatus(sessionId, "stopped")
|
|
357
|
+
return { sessionId, reply: "longagent stopped", usage: aggregateUsage, toolEvents, iterations: iteration, status: "stopped" }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const fullPrompt = buildStageWrapper(stage, stageContext, prompt, stuckWarningMsg)
|
|
361
|
+
if (stuckWarningMsg) stuckWarningMsg = null
|
|
362
|
+
const readonly = stage === LONGAGENT_4STAGE_STAGES.PREVIEW || stage === LONGAGENT_4STAGE_STAGES.BLUEPRINT
|
|
363
|
+
const stageAgentName = readonly
|
|
364
|
+
? (stage === LONGAGENT_4STAGE_STAGES.PREVIEW ? "preview-agent" : "blueprint-agent")
|
|
365
|
+
: (stage === LONGAGENT_4STAGE_STAGES.CODING ? "coding-agent" : "debugging-agent")
|
|
366
|
+
|
|
367
|
+
const out = await processTurnLoop({
|
|
368
|
+
prompt: fullPrompt, mode: "agent", agent: getAgent(stageAgentName),
|
|
369
|
+
model: stageModel, providerType: stageProvider, sessionId, configState,
|
|
370
|
+
baseUrl, apiKeyEnv, signal, output, allowQuestion, toolContext
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
aggregateUsage.input += out.usage.input || 0
|
|
374
|
+
aggregateUsage.output += out.usage.output || 0
|
|
375
|
+
if (out.toolEvents?.length) toolEvents.push(...out.toolEvents)
|
|
376
|
+
finalReply = out.reply
|
|
377
|
+
|
|
378
|
+
// 防卡死检测
|
|
379
|
+
if (out.toolEvents?.length) {
|
|
380
|
+
const stuckResult = stuckTracker.track(out.toolEvents)
|
|
381
|
+
if (stuckResult.isStuck) {
|
|
382
|
+
const readonly = stage === LONGAGENT_4STAGE_STAGES.PREVIEW || stage === LONGAGENT_4STAGE_STAGES.BLUEPRINT
|
|
383
|
+
stuckWarningMsg = readonly
|
|
384
|
+
? `[STUCK DETECTION] You have been exploring files for too many rounds without progress. STOP reading more files. Synthesize what you've learned and COMPLETE this stage now.`
|
|
385
|
+
: `[STUCK DETECTION] You appear stuck in an exploration loop. STOP reading files and START implementing. Make concrete changes to files.`
|
|
386
|
+
stuckTracker.resetReadOnlyCount()
|
|
387
|
+
await EventBus.emit({
|
|
388
|
+
type: EVENT_TYPES.LONGAGENT_ALERT, sessionId,
|
|
389
|
+
payload: { kind: "stuck_warning", stage, reason: stuckResult.reason, iteration: currentStageIteration }
|
|
390
|
+
})
|
|
391
|
+
await syncState({ lastMessage: `stuck detected at ${stage}, iter ${currentStageIteration}` })
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (detectStageComplete(out.reply, stage)) {
|
|
396
|
+
stageComplete = true
|
|
397
|
+
stageContext[stage] = out.reply
|
|
398
|
+
await emitStageComplete(stage)
|
|
399
|
+
if (gitActive && gitConfig.auto_commit_stages !== false) {
|
|
400
|
+
await git.commitAll(`[kkcode] 4-stage: ${stage} completed`, cwd).catch(() => {})
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Debugging → Coding回退(带次数限制)
|
|
405
|
+
if (stage === LONGAGENT_4STAGE_STAGES.DEBUGGING && detectReturnToCoding(out.reply)) {
|
|
406
|
+
codingRollbackCount++
|
|
407
|
+
if (codingRollbackCount > maxCodingRollbacks) {
|
|
408
|
+
await EventBus.emit({ type: EVENT_TYPES.LONGAGENT_ALERT, sessionId, payload: { kind: "rollback_limit", message: `coding rollback limit (${maxCodingRollbacks}) reached, forcing completion` } })
|
|
409
|
+
stageComplete = true
|
|
410
|
+
continue
|
|
411
|
+
}
|
|
412
|
+
await EventBus.emit({ type: EVENT_TYPES.LONGAGENT_4STAGE_RETURN_TO_CODING, sessionId, payload: { rollbackCount: codingRollbackCount } })
|
|
413
|
+
stageIndex = stageOrder.indexOf(LONGAGENT_4STAGE_STAGES.CODING)
|
|
414
|
+
stageComplete = true
|
|
415
|
+
continue
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (/\[TASK_COMPLETE\]/i.test(out.reply)) completionMarkerSeen = true
|
|
419
|
+
await syncState({ lastMessage: `stage ${stage}, iteration ${currentStageIteration}/${maxIter}` })
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!stageComplete) {
|
|
423
|
+
await LongAgentManager.update(sessionId, { status: "failed", lastMessage: `stage ${stage} timed out after ${maxIter} iterations` })
|
|
424
|
+
return { sessionId, reply: `stage ${stage} timed out`, usage: aggregateUsage, toolEvents, iterations: iteration, status: "failed" }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
stageIndex += 1
|
|
428
|
+
await saveCheckpoint(sessionId, { name: `4stage_${stage}`, iteration, currentStage, stageContext })
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Git merge (only if not failed)
|
|
432
|
+
if (gitActive && gitBaseBranch && gitBranch) {
|
|
433
|
+
try {
|
|
434
|
+
await git.commitAll(`[kkcode] 4-stage session ${sessionId} completed`, cwd)
|
|
435
|
+
if (gitConfig.auto_merge !== false) {
|
|
436
|
+
await LongAgentManager.withLock(async () => {
|
|
437
|
+
const doneState = await LongAgentManager.get(sessionId)
|
|
438
|
+
if (doneState?.status !== "failed") {
|
|
439
|
+
await git.checkoutBranch(gitBaseBranch, cwd)
|
|
440
|
+
const mergeResult = await git.mergeBranch(gitBranch, cwd)
|
|
441
|
+
if (mergeResult.ok) {
|
|
442
|
+
await git.deleteBranch(gitBranch, cwd)
|
|
443
|
+
} else {
|
|
444
|
+
// Merge failed — stay on feature branch
|
|
445
|
+
await git.checkoutBranch(gitBranch, cwd).catch(() => {})
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}, cwd)
|
|
449
|
+
}
|
|
450
|
+
} catch { /* git merge best-effort */ }
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
await LongAgentManager.update(sessionId, { status: completionMarkerSeen ? "completed" : "done", lastMessage: "4-stage longagent complete" })
|
|
454
|
+
await markSessionStatus(sessionId, completionMarkerSeen ? "completed" : "active")
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
sessionId,
|
|
458
|
+
turnId: `turn_long_${Date.now()}`,
|
|
459
|
+
reply: finalReply || "4-stage longagent complete",
|
|
460
|
+
usage: aggregateUsage,
|
|
461
|
+
toolEvents,
|
|
462
|
+
iterations: iteration,
|
|
463
|
+
status: completionMarkerSeen ? "completed" : "done",
|
|
464
|
+
elapsed: Math.round((Date.now() - startTime) / 1000),
|
|
465
|
+
fourStage: { completed: true, stageContext }
|
|
466
|
+
}
|
|
467
|
+
}
|