@kkelly-offical/kkcode 0.1.7 → 0.2.3-preview.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 +474 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +228 -220
- 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 +89 -89
- 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/commands/update.mjs +32 -0
- package/src/config/defaults.mjs +289 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +604 -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 +87 -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 +4 -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 +3371 -2981
- 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 +298 -298
- package/src/session/engine.mjs +417 -232
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1097
- 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 -900
- package/src/session/loop.mjs +1005 -930
- package/src/session/prompt/agent.txt +25 -25
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +31 -31
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +196 -195
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -519
- package/src/session/system-prompt.mjs +308 -273
- 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 +17 -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 +99 -93
- 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/update/checker.mjs +184 -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
- package/src/version.mjs +3 -0
package/src/session/engine.mjs
CHANGED
|
@@ -1,232 +1,417 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto"
|
|
2
|
-
import { loadPricing, calculateCost } from "../usage/pricing.mjs"
|
|
3
|
-
import { recordTurn } from "../usage/usage-meter.mjs"
|
|
4
|
-
import { processTurnLoop } from "./loop.mjs"
|
|
5
|
-
import { runLongAgent } from "./longagent.mjs"
|
|
6
|
-
import { touchSession, setBudgetState } from "./store.mjs"
|
|
7
|
-
import { appendEventLog } from "../storage/event-log.mjs"
|
|
8
|
-
import { EventBus } from "../core/events.mjs"
|
|
9
|
-
import { initialize as initObservability } from "../observability/index.mjs"
|
|
10
|
-
import { ToolRegistry } from "../tool/registry.mjs"
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
1
|
+
import { randomUUID } from "node:crypto"
|
|
2
|
+
import { loadPricing, calculateCost } from "../usage/pricing.mjs"
|
|
3
|
+
import { recordTurn } from "../usage/usage-meter.mjs"
|
|
4
|
+
import { processTurnLoop } from "./loop.mjs"
|
|
5
|
+
import { runLongAgent } from "./longagent.mjs"
|
|
6
|
+
import { touchSession, setBudgetState } from "./store.mjs"
|
|
7
|
+
import { appendEventLog } from "../storage/event-log.mjs"
|
|
8
|
+
import { EventBus } from "../core/events.mjs"
|
|
9
|
+
import { initialize as initObservability } from "../observability/index.mjs"
|
|
10
|
+
import { ToolRegistry } from "../tool/registry.mjs"
|
|
11
|
+
import { SkillRegistry } from "../skill/registry.mjs"
|
|
12
|
+
import { resolveAgentForMode } from "../agent/agent.mjs"
|
|
13
|
+
import { estimateStringTokens } from "./compaction.mjs"
|
|
14
|
+
import { classifyTaskMode, explainTaskModeReason } from "./longagent-utils.mjs"
|
|
15
|
+
|
|
16
|
+
let sinkReady = false
|
|
17
|
+
|
|
18
|
+
export const PUBLIC_MODE_CONTRACT = Object.freeze([
|
|
19
|
+
{
|
|
20
|
+
mode: "assistant",
|
|
21
|
+
summary: "default CLI personal assistant lane",
|
|
22
|
+
guarantee: "assistant handles bounded terminal-native personal assistant work under normal tool permissions"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
mode: "plan",
|
|
26
|
+
summary: "produce a spec/plan only",
|
|
27
|
+
guarantee: "plan does not execute file mutations"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
mode: "agent",
|
|
31
|
+
summary: "dedicated coding execution lane",
|
|
32
|
+
guarantee: "agent is the dedicated lane for bounded coding inspect/patch/verify work"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
mode: "longagent",
|
|
36
|
+
summary: "heavyweight staged multi-file delivery lane",
|
|
37
|
+
guarantee: "longagent stays reserved for structured multi-file or system-level work"
|
|
38
|
+
}
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
function estimateTokens(text) {
|
|
42
|
+
return Math.max(1, estimateStringTokens(text || ""))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveMode(inputMode = "assistant") {
|
|
46
|
+
const mode = String(inputMode || "assistant").toLowerCase()
|
|
47
|
+
if (mode === "code" || mode === "coding") return "agent"
|
|
48
|
+
if (mode === "ask") return "assistant"
|
|
49
|
+
if (["assistant", "plan", "agent", "longagent"].includes(mode)) return mode
|
|
50
|
+
return "assistant"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getPublicModeContract(inputMode = "assistant") {
|
|
54
|
+
const mode = resolveMode(inputMode)
|
|
55
|
+
return PUBLIC_MODE_CONTRACT.find((item) => item.mode === mode) || PUBLIC_MODE_CONTRACT[0]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatPublicModeSummary(inputMode = "assistant") {
|
|
59
|
+
const contract = getPublicModeContract(inputMode)
|
|
60
|
+
return `${contract.mode}: ${contract.summary}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function renderPublicModeContract() {
|
|
64
|
+
return [
|
|
65
|
+
"# Mode Contract",
|
|
66
|
+
"",
|
|
67
|
+
"- `assistant`: default CLI personal assistant lane for bounded terminal-native personal work, explanation, and analysis.",
|
|
68
|
+
"- `plan`: produce a spec/plan only; do not execute file mutations.",
|
|
69
|
+
"- `agent` / `code` / `coding`: dedicated coding lane for inspect/patch/verify work.",
|
|
70
|
+
"- `longagent`: heavyweight staged multi-file delivery lane with explicit gates.",
|
|
71
|
+
"- Route from `assistant` to `agent` when coding mutation, debugging, or test work is explicit.",
|
|
72
|
+
"- Upgrade from `assistant` or `agent` to `longagent` only when heavy multi-file or system-level evidence appears.",
|
|
73
|
+
"- Keep `plan` explicit and mutation-free even when later execution is likely."
|
|
74
|
+
].join("\n")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function summarizeRouteEvidence(classification) {
|
|
78
|
+
const evidence = Array.isArray(classification?.evidence) ? classification.evidence : []
|
|
79
|
+
if (!evidence.length) return "evidence=none"
|
|
80
|
+
return `evidence=${evidence.join(", ")}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function summarizeRouteTopology(classification) {
|
|
84
|
+
const topology = classification?.topology || "open_ended"
|
|
85
|
+
const continuity = classification?.continuity || "new_transaction"
|
|
86
|
+
return `topology=${topology}; continuity=${continuity}`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function summarizeRouteDecision(route) {
|
|
90
|
+
if (!route) return ""
|
|
91
|
+
const parts = [summarizeRouteTopology(route), summarizeRouteEvidence(route)]
|
|
92
|
+
if (route.suggestion) parts.push(`upgrade_path=${route.mode}->${route.suggestion}`)
|
|
93
|
+
return parts.join("; ")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 智能模式路由:根据 prompt 内容判断最适合的执行模式
|
|
98
|
+
* @returns {{ mode: string, changed: boolean, reason: string, confidence: string, forced: boolean }}
|
|
99
|
+
* forced=true 表示用户强制使用了不匹配的模式(需要确认)
|
|
100
|
+
*/
|
|
101
|
+
function finalizeRouteDecision(req, classification, base = {}) {
|
|
102
|
+
const effectiveMode = base.changed ? base.mode : req
|
|
103
|
+
const evidenceSummary = summarizeRouteEvidence(classification)
|
|
104
|
+
const topologySummary = summarizeRouteTopology(classification)
|
|
105
|
+
const upgradePath = base.suggestion ? `${effectiveMode}->${base.suggestion}` : null
|
|
106
|
+
return {
|
|
107
|
+
...base,
|
|
108
|
+
modeContract: getPublicModeContract(effectiveMode),
|
|
109
|
+
topology: classification.topology || "open_ended",
|
|
110
|
+
evidence: Array.isArray(classification.evidence) ? classification.evidence : [],
|
|
111
|
+
pathHints: Array.isArray(classification.pathHints) ? classification.pathHints : [],
|
|
112
|
+
continuity: classification.continuity || "new_transaction",
|
|
113
|
+
evidenceSummary,
|
|
114
|
+
topologySummary,
|
|
115
|
+
upgradePath,
|
|
116
|
+
observability: {
|
|
117
|
+
requestedMode: req,
|
|
118
|
+
effectiveMode,
|
|
119
|
+
suggestedMode: classification.mode,
|
|
120
|
+
changed: Boolean(base.changed),
|
|
121
|
+
forced: Boolean(base.forced),
|
|
122
|
+
suggestion: base.suggestion || null,
|
|
123
|
+
modeContract: getPublicModeContract(effectiveMode),
|
|
124
|
+
reason: base.reason,
|
|
125
|
+
confidence: base.confidence,
|
|
126
|
+
topology: classification.topology || "open_ended",
|
|
127
|
+
evidence: Array.isArray(classification.evidence) ? classification.evidence : [],
|
|
128
|
+
pathHints: Array.isArray(classification.pathHints) ? classification.pathHints : [],
|
|
129
|
+
continuity: classification.continuity || "new_transaction",
|
|
130
|
+
evidenceSummary,
|
|
131
|
+
topologySummary,
|
|
132
|
+
upgradePath,
|
|
133
|
+
stayedLocal: (effectiveMode === "assistant" && classification.mode === "assistant") || (effectiveMode === "agent" && classification.mode === "agent"),
|
|
134
|
+
deferredLongagent: (req === "assistant" || req === "agent") && base.suggestion === "longagent",
|
|
135
|
+
overEscalatedToLongagent: req === "longagent" && classification.mode === "agent"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function routeMode(prompt, requestedMode, options = {}) {
|
|
141
|
+
const req = resolveMode(requestedMode)
|
|
142
|
+
// plan 模式不参与自动路由
|
|
143
|
+
if (req === "plan") {
|
|
144
|
+
return finalizeRouteDecision(req, {
|
|
145
|
+
mode: req,
|
|
146
|
+
topology: "open_ended",
|
|
147
|
+
evidence: [],
|
|
148
|
+
pathHints: [],
|
|
149
|
+
continuity: "new_transaction"
|
|
150
|
+
}, {
|
|
151
|
+
mode: req,
|
|
152
|
+
changed: false,
|
|
153
|
+
reason: "plan_mode_exempt",
|
|
154
|
+
explanation: explainTaskModeReason("plan_mode_exempt"),
|
|
155
|
+
confidence: "high",
|
|
156
|
+
forced: false
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const classification = classifyTaskMode(prompt, options)
|
|
161
|
+
const suggested = classification.mode
|
|
162
|
+
const explanation = classification.explanation || explainTaskModeReason(classification.reason)
|
|
163
|
+
|
|
164
|
+
// 相同模式,无需路由
|
|
165
|
+
if (suggested === req) {
|
|
166
|
+
return finalizeRouteDecision(req, classification, { mode: req, changed: false, reason: classification.reason, explanation, confidence: classification.confidence, forced: false })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 低置信度不自动路由
|
|
170
|
+
if (classification.confidence === "low") {
|
|
171
|
+
return finalizeRouteDecision(req, classification, { mode: req, changed: false, reason: "low_confidence", explanation: explainTaskModeReason("low_confidence"), confidence: "low", forced: false })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 高置信度:assistant/agent 模式下检测到 longagent 任务 → 建议切换(无需确认,只提示)
|
|
175
|
+
if ((req === "assistant" || req === "agent") && suggested === "longagent" && classification.confidence === "high") {
|
|
176
|
+
return finalizeRouteDecision(req, classification, { mode: req, changed: false, reason: classification.reason, explanation, confidence: "high", forced: false, suggestion: "longagent" })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// assistant 模式下检测到明确编码任务 → 自动切换到专门 coding lane
|
|
180
|
+
if (req === "assistant" && suggested === "agent" && classification.confidence !== "low") {
|
|
181
|
+
return finalizeRouteDecision(req, classification, { mode: "agent", changed: true, reason: classification.reason, explanation, confidence: classification.confidence, forced: false })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// agent 模式下检测到通用问答 / 分析任务 → 回到通用 assistant lane
|
|
185
|
+
if (req === "agent" && suggested === "assistant" && classification.confidence !== "low") {
|
|
186
|
+
return finalizeRouteDecision(req, classification, { mode: "assistant", changed: true, reason: classification.reason, explanation, confidence: classification.confidence, forced: false })
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 高置信度:用户强制 longagent 但任务是简单 agent 任务 → 需要确认
|
|
190
|
+
if (req === "longagent" && suggested === "agent" && classification.confidence === "high") {
|
|
191
|
+
return finalizeRouteDecision(req, classification, { mode: req, changed: false, reason: classification.reason, explanation, confidence: "high", forced: true, suggestion: "agent" })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return finalizeRouteDecision(req, classification, { mode: req, changed: false, reason: classification.reason, explanation, confidence: classification.confidence, forced: false })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function resolvePromptMode(prompt, requestedMode = "agent", options = {}) {
|
|
198
|
+
const requested = resolveMode(requestedMode)
|
|
199
|
+
const route = routeMode(prompt, requested, options)
|
|
200
|
+
return {
|
|
201
|
+
requestedMode: requested,
|
|
202
|
+
effectiveMode: route.changed ? route.mode : requested,
|
|
203
|
+
effectiveContract: getPublicModeContract(route.changed ? route.mode : requested),
|
|
204
|
+
route
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function newSessionId() {
|
|
209
|
+
return `ses_${randomUUID().slice(0, 12)}`
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function ensureEventSinks() {
|
|
213
|
+
if (sinkReady) return
|
|
214
|
+
EventBus.registerSink(async (event) => {
|
|
215
|
+
await appendEventLog(event)
|
|
216
|
+
})
|
|
217
|
+
initObservability(EventBus)
|
|
218
|
+
sinkReady = true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function evaluateBudget(config, meter) {
|
|
222
|
+
const budget = config.usage?.budget || {}
|
|
223
|
+
const warnings = []
|
|
224
|
+
const strategy = budget.strategy || "warn"
|
|
225
|
+
const warnAt = Number(budget.warn_at_percent || 80)
|
|
226
|
+
let exceeded = false
|
|
227
|
+
|
|
228
|
+
if (budget.session_usd && meter.session.cost > 0) {
|
|
229
|
+
const ratio = (meter.session.cost / budget.session_usd) * 100
|
|
230
|
+
if (ratio >= 100) exceeded = true
|
|
231
|
+
if (ratio >= warnAt) warnings.push(`session budget ${ratio.toFixed(1)}% (${meter.session.cost.toFixed(4)}/${budget.session_usd})`)
|
|
232
|
+
}
|
|
233
|
+
if (budget.global_usd && meter.global.cost > 0) {
|
|
234
|
+
const ratio = (meter.global.cost / budget.global_usd) * 100
|
|
235
|
+
if (ratio >= 100) exceeded = true
|
|
236
|
+
if (ratio >= warnAt) warnings.push(`global budget ${ratio.toFixed(1)}% (${meter.global.cost.toFixed(4)}/${budget.global_usd})`)
|
|
237
|
+
}
|
|
238
|
+
return { warnings, exceeded, strategy }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function executeTurn({
|
|
242
|
+
prompt,
|
|
243
|
+
contentBlocks = null,
|
|
244
|
+
mode,
|
|
245
|
+
model,
|
|
246
|
+
sessionId,
|
|
247
|
+
configState,
|
|
248
|
+
providerType = null,
|
|
249
|
+
baseUrl = null,
|
|
250
|
+
apiKeyEnv = null,
|
|
251
|
+
maxIterations = null,
|
|
252
|
+
signal = null,
|
|
253
|
+
output = null,
|
|
254
|
+
allowQuestion = true,
|
|
255
|
+
toolContext = {},
|
|
256
|
+
longagentImpl = null
|
|
257
|
+
}) {
|
|
258
|
+
ensureEventSinks()
|
|
259
|
+
|
|
260
|
+
const resolvedProviderType = providerType || configState.config.provider.default
|
|
261
|
+
const agent = resolveAgentForMode(mode)
|
|
262
|
+
await ToolRegistry.initialize({
|
|
263
|
+
config: configState.config,
|
|
264
|
+
cwd: process.cwd()
|
|
265
|
+
})
|
|
266
|
+
await SkillRegistry.initialize(configState.config, process.cwd())
|
|
267
|
+
// Auto-name session from first user prompt (truncated to 50 chars)
|
|
268
|
+
const autoTitle = typeof prompt === "string"
|
|
269
|
+
? prompt.replace(/\s+/g, " ").trim().slice(0, 50)
|
|
270
|
+
: null
|
|
271
|
+
await touchSession({
|
|
272
|
+
sessionId,
|
|
273
|
+
mode,
|
|
274
|
+
model,
|
|
275
|
+
providerType: resolvedProviderType,
|
|
276
|
+
cwd: process.cwd(),
|
|
277
|
+
title: autoTitle || null,
|
|
278
|
+
status: mode === "longagent" ? "running-longagent" : "active"
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const turn =
|
|
282
|
+
mode === "longagent"
|
|
283
|
+
? await runLongAgent({
|
|
284
|
+
prompt,
|
|
285
|
+
model,
|
|
286
|
+
providerType: resolvedProviderType,
|
|
287
|
+
sessionId,
|
|
288
|
+
configState,
|
|
289
|
+
baseUrl,
|
|
290
|
+
apiKeyEnv,
|
|
291
|
+
agent,
|
|
292
|
+
maxIterations:
|
|
293
|
+
maxIterations === null
|
|
294
|
+
? Number(configState.config.agent.longagent.max_iterations || 0)
|
|
295
|
+
: Number(maxIterations),
|
|
296
|
+
signal,
|
|
297
|
+
output,
|
|
298
|
+
allowQuestion,
|
|
299
|
+
toolContext,
|
|
300
|
+
longagentImpl
|
|
301
|
+
})
|
|
302
|
+
: await processTurnLoop({
|
|
303
|
+
prompt,
|
|
304
|
+
contentBlocks,
|
|
305
|
+
mode,
|
|
306
|
+
model,
|
|
307
|
+
providerType: resolvedProviderType,
|
|
308
|
+
sessionId,
|
|
309
|
+
configState,
|
|
310
|
+
baseUrl,
|
|
311
|
+
apiKeyEnv,
|
|
312
|
+
agent,
|
|
313
|
+
output,
|
|
314
|
+
signal,
|
|
315
|
+
allowQuestion,
|
|
316
|
+
toolContext
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
const usage = { ...turn.usage }
|
|
320
|
+
let estimated = false
|
|
321
|
+
if ((usage.input || 0) === 0 && (usage.output || 0) === 0) {
|
|
322
|
+
usage.input = estimateTokens(prompt)
|
|
323
|
+
usage.output = estimateTokens(turn.reply)
|
|
324
|
+
estimated = true
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const pricingInfo = await loadPricing(configState)
|
|
328
|
+
const costInfo = calculateCost(pricingInfo.pricing, model, usage)
|
|
329
|
+
const meter = await recordTurn({ sessionId, usage, cost: costInfo.amount })
|
|
330
|
+
const budgetResult = evaluateBudget(configState.config, meter)
|
|
331
|
+
|
|
332
|
+
await setBudgetState(sessionId, {
|
|
333
|
+
lastTurnCost: costInfo.amount,
|
|
334
|
+
warnings: budgetResult.warnings,
|
|
335
|
+
exceeded: budgetResult.exceeded,
|
|
336
|
+
updatedAt: Date.now()
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
if (budgetResult.exceeded && budgetResult.strategy === "block") {
|
|
340
|
+
const msg = `budget exceeded — ${budgetResult.warnings.join("; ")}. strategy=block, stopping execution.`
|
|
341
|
+
return {
|
|
342
|
+
reply: msg,
|
|
343
|
+
mode,
|
|
344
|
+
model,
|
|
345
|
+
sessionId,
|
|
346
|
+
turnId: turn.turnId,
|
|
347
|
+
emittedText: turn.emittedText,
|
|
348
|
+
context: turn.context,
|
|
349
|
+
tokenMeter: { ...meter, estimated: estimated || costInfo.unknown },
|
|
350
|
+
cost: costInfo.amount,
|
|
351
|
+
costSavings: costInfo.savings,
|
|
352
|
+
pricingWarnings: pricingInfo.errors,
|
|
353
|
+
budgetWarnings: budgetResult.warnings,
|
|
354
|
+
budgetExceeded: true,
|
|
355
|
+
toolEvents: turn.toolEvents,
|
|
356
|
+
longagent: mode === "longagent"
|
|
357
|
+
? {
|
|
358
|
+
status: turn.status,
|
|
359
|
+
phase: turn.phase,
|
|
360
|
+
gateStatus: turn.gateStatus,
|
|
361
|
+
currentGate: turn.currentGate,
|
|
362
|
+
lastGateFailures: turn.lastGateFailures || [],
|
|
363
|
+
iterations: turn.iterations,
|
|
364
|
+
recoveryCount: turn.recoveryCount,
|
|
365
|
+
progress: turn.progress,
|
|
366
|
+
elapsed: turn.elapsed,
|
|
367
|
+
stageIndex: turn.stageIndex,
|
|
368
|
+
stageCount: turn.stageCount,
|
|
369
|
+
currentStageId: turn.currentStageId,
|
|
370
|
+
planFrozen: turn.planFrozen,
|
|
371
|
+
taskProgress: turn.taskProgress,
|
|
372
|
+
stageProgress: turn.stageProgress,
|
|
373
|
+
remainingFilesCount: turn.remainingFilesCount,
|
|
374
|
+
fileChanges: turn.fileChanges || []
|
|
375
|
+
}
|
|
376
|
+
: null
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
reply: turn.reply,
|
|
382
|
+
mode,
|
|
383
|
+
model,
|
|
384
|
+
sessionId,
|
|
385
|
+
turnId: turn.turnId,
|
|
386
|
+
emittedText: turn.emittedText,
|
|
387
|
+
context: turn.context,
|
|
388
|
+
tokenMeter: { ...meter, estimated: estimated || costInfo.unknown },
|
|
389
|
+
cost: costInfo.amount,
|
|
390
|
+
costSavings: costInfo.savings,
|
|
391
|
+
pricingWarnings: pricingInfo.errors,
|
|
392
|
+
budgetWarnings: budgetResult.warnings,
|
|
393
|
+
budgetExceeded: false,
|
|
394
|
+
toolEvents: turn.toolEvents,
|
|
395
|
+
longagent: mode === "longagent"
|
|
396
|
+
? {
|
|
397
|
+
status: turn.status,
|
|
398
|
+
phase: turn.phase,
|
|
399
|
+
gateStatus: turn.gateStatus,
|
|
400
|
+
currentGate: turn.currentGate,
|
|
401
|
+
lastGateFailures: turn.lastGateFailures || [],
|
|
402
|
+
iterations: turn.iterations,
|
|
403
|
+
recoveryCount: turn.recoveryCount,
|
|
404
|
+
progress: turn.progress,
|
|
405
|
+
elapsed: turn.elapsed,
|
|
406
|
+
stageIndex: turn.stageIndex,
|
|
407
|
+
stageCount: turn.stageCount,
|
|
408
|
+
currentStageId: turn.currentStageId,
|
|
409
|
+
planFrozen: turn.planFrozen,
|
|
410
|
+
taskProgress: turn.taskProgress,
|
|
411
|
+
stageProgress: turn.stageProgress,
|
|
412
|
+
remainingFilesCount: turn.remainingFilesCount,
|
|
413
|
+
fileChanges: turn.fileChanges || []
|
|
414
|
+
}
|
|
415
|
+
: null
|
|
416
|
+
}
|
|
417
|
+
}
|