@qlingzzy/qling 1.0.0 → 1.1.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.
Files changed (254) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.en.md +83 -18
  3. package/README.md +19 -9
  4. package/dist/agent/main-loop.d.ts +120 -0
  5. package/dist/agent/main-loop.d.ts.map +1 -0
  6. package/dist/agent/main-loop.js +279 -0
  7. package/dist/agent/subtask-parallel.d.ts +29 -0
  8. package/dist/agent/subtask-parallel.d.ts.map +1 -0
  9. package/dist/agent/subtask-parallel.js +107 -0
  10. package/dist/agent/subtask.d.ts +13 -0
  11. package/dist/agent/subtask.d.ts.map +1 -1
  12. package/dist/agent/subtask.js +79 -24
  13. package/dist/agent/system-prompt.d.ts +39 -0
  14. package/dist/agent/system-prompt.d.ts.map +1 -0
  15. package/dist/agent/system-prompt.js +89 -0
  16. package/dist/agent/tool-orchestrator.d.ts +66 -0
  17. package/dist/agent/tool-orchestrator.d.ts.map +1 -0
  18. package/dist/agent/tool-orchestrator.js +355 -0
  19. package/dist/agent-loop.d.ts +29 -16
  20. package/dist/agent-loop.d.ts.map +1 -1
  21. package/dist/agent-loop.js +262 -807
  22. package/dist/agents/roles.d.ts +57 -0
  23. package/dist/agents/roles.d.ts.map +1 -0
  24. package/dist/agents/roles.js +198 -0
  25. package/dist/commands/agents.d.ts.map +1 -1
  26. package/dist/commands/agents.js +17 -3
  27. package/dist/commands/claude-style.d.ts.map +1 -1
  28. package/dist/commands/claude-style.js +19 -10
  29. package/dist/commands/help.d.ts.map +1 -1
  30. package/dist/commands/help.js +3 -2
  31. package/dist/commands/index.d.ts.map +1 -1
  32. package/dist/commands/index.js +6 -0
  33. package/dist/commands/mode.d.ts +16 -0
  34. package/dist/commands/mode.d.ts.map +1 -0
  35. package/dist/commands/mode.js +67 -0
  36. package/dist/commands/recover.d.ts +3 -0
  37. package/dist/commands/recover.d.ts.map +1 -0
  38. package/dist/commands/recover.js +69 -0
  39. package/dist/commands/runtime.d.ts +2 -0
  40. package/dist/commands/runtime.d.ts.map +1 -1
  41. package/dist/commands/runtime.js +2 -0
  42. package/dist/commands/trace.d.ts +3 -0
  43. package/dist/commands/trace.d.ts.map +1 -0
  44. package/dist/commands/trace.js +45 -0
  45. package/dist/commands/verify.js +2 -2
  46. package/dist/config-report.d.ts +0 -1
  47. package/dist/config-report.d.ts.map +1 -1
  48. package/dist/config-report.js +1 -2
  49. package/dist/config.d.ts +0 -1
  50. package/dist/config.d.ts.map +1 -1
  51. package/dist/config.js +0 -1
  52. package/dist/context-report.d.ts +13 -7
  53. package/dist/context-report.d.ts.map +1 -1
  54. package/dist/context-report.js +64 -61
  55. package/dist/context-tool-hygiene.d.ts +36 -0
  56. package/dist/context-tool-hygiene.d.ts.map +1 -0
  57. package/dist/context-tool-hygiene.js +125 -0
  58. package/dist/dashboard/client.d.ts +2 -0
  59. package/dist/dashboard/client.d.ts.map +1 -0
  60. package/dist/dashboard/client.js +305 -0
  61. package/dist/dashboard/model.d.ts +13 -0
  62. package/dist/dashboard/model.d.ts.map +1 -0
  63. package/dist/dashboard/model.js +112 -0
  64. package/dist/dashboard/page.d.ts +3 -0
  65. package/dist/dashboard/page.d.ts.map +1 -0
  66. package/dist/dashboard/page.js +204 -0
  67. package/dist/dashboard/types.d.ts +63 -0
  68. package/dist/dashboard/types.d.ts.map +1 -0
  69. package/dist/dashboard/types.js +1 -0
  70. package/dist/dashboard-server.d.ts +24 -4
  71. package/dist/dashboard-server.d.ts.map +1 -1
  72. package/dist/dashboard-server.js +397 -302
  73. package/dist/discovery-registry.d.ts +17 -3
  74. package/dist/discovery-registry.d.ts.map +1 -1
  75. package/dist/discovery-registry.js +78 -3
  76. package/dist/doctor.d.ts +8 -0
  77. package/dist/doctor.d.ts.map +1 -1
  78. package/dist/doctor.js +116 -0
  79. package/dist/eval/llm-tasks.d.ts +5 -0
  80. package/dist/eval/llm-tasks.d.ts.map +1 -0
  81. package/dist/eval/llm-tasks.js +125 -0
  82. package/dist/eval/repo-tasks.d.ts +12 -0
  83. package/dist/eval/repo-tasks.d.ts.map +1 -0
  84. package/dist/eval/repo-tasks.js +220 -0
  85. package/dist/eval/runner.d.ts +3 -1
  86. package/dist/eval/runner.d.ts.map +1 -1
  87. package/dist/eval/runner.js +23 -4
  88. package/dist/eval/tasks.d.ts.map +1 -1
  89. package/dist/eval/tasks.js +186 -0
  90. package/dist/eval/types.d.ts +8 -5
  91. package/dist/eval/types.d.ts.map +1 -1
  92. package/dist/execution/event-bus.d.ts +44 -0
  93. package/dist/execution/event-bus.d.ts.map +1 -0
  94. package/dist/execution/event-bus.js +76 -0
  95. package/dist/execution/failure-classifier.d.ts +11 -0
  96. package/dist/execution/failure-classifier.d.ts.map +1 -0
  97. package/dist/execution/failure-classifier.js +66 -0
  98. package/dist/execution/progress-detector.d.ts +3 -0
  99. package/dist/execution/progress-detector.d.ts.map +1 -0
  100. package/dist/execution/progress-detector.js +23 -0
  101. package/dist/execution/recovery-controller.d.ts +24 -0
  102. package/dist/execution/recovery-controller.d.ts.map +1 -0
  103. package/dist/execution/recovery-controller.js +114 -0
  104. package/dist/execution/recovery-messages.d.ts +24 -0
  105. package/dist/execution/recovery-messages.d.ts.map +1 -0
  106. package/dist/execution/recovery-messages.js +48 -0
  107. package/dist/execution/recovery-metrics.d.ts +16 -0
  108. package/dist/execution/recovery-metrics.d.ts.map +1 -0
  109. package/dist/execution/recovery-metrics.js +73 -0
  110. package/dist/execution/recovery-strategy-planner.d.ts +6 -0
  111. package/dist/execution/recovery-strategy-planner.d.ts.map +1 -0
  112. package/dist/execution/recovery-strategy-planner.js +24 -0
  113. package/dist/execution/run-trace-store.d.ts +29 -0
  114. package/dist/execution/run-trace-store.d.ts.map +1 -0
  115. package/dist/execution/run-trace-store.js +140 -0
  116. package/dist/execution/staged-verifier.d.ts +33 -0
  117. package/dist/execution/staged-verifier.d.ts.map +1 -0
  118. package/dist/execution/staged-verifier.js +39 -0
  119. package/dist/execution/types.d.ts +58 -0
  120. package/dist/execution/types.d.ts.map +1 -0
  121. package/dist/execution/types.js +1 -0
  122. package/dist/execution/verification-loop.d.ts +58 -0
  123. package/dist/execution/verification-loop.d.ts.map +1 -0
  124. package/dist/execution/verification-loop.js +195 -0
  125. package/dist/execution/verification-stages.d.ts +17 -0
  126. package/dist/execution/verification-stages.d.ts.map +1 -0
  127. package/dist/execution/verification-stages.js +102 -0
  128. package/dist/help-topics.d.ts.map +1 -1
  129. package/dist/help-topics.js +11 -0
  130. package/dist/index.js +0 -8
  131. package/dist/lsp/ts-service.d.ts +36 -0
  132. package/dist/lsp/ts-service.d.ts.map +1 -0
  133. package/dist/lsp/ts-service.js +291 -0
  134. package/dist/memory/lifecycle.d.ts +19 -0
  135. package/dist/memory/lifecycle.d.ts.map +1 -0
  136. package/dist/memory/lifecycle.js +53 -0
  137. package/dist/memory.d.ts +0 -16
  138. package/dist/memory.d.ts.map +1 -1
  139. package/dist/memory.js +0 -36
  140. package/dist/metrics/collector.d.ts +7 -0
  141. package/dist/metrics/collector.d.ts.map +1 -1
  142. package/dist/metrics/collector.js +60 -0
  143. package/dist/mission/manager.d.ts +1 -0
  144. package/dist/mission/manager.d.ts.map +1 -1
  145. package/dist/mission/manager.js +9 -0
  146. package/dist/mission/progress-notify.d.ts +39 -0
  147. package/dist/mission/progress-notify.d.ts.map +1 -0
  148. package/dist/mission/progress-notify.js +386 -0
  149. package/dist/pipeline/hooks.d.ts +1 -1
  150. package/dist/pipeline/hooks.d.ts.map +1 -1
  151. package/dist/pipeline/hooks.js +1 -0
  152. package/dist/pipeline/sections.d.ts +8 -4
  153. package/dist/pipeline/sections.d.ts.map +1 -1
  154. package/dist/pipeline/sections.js +70 -27
  155. package/dist/pipeline/verification.d.ts +1 -0
  156. package/dist/pipeline/verification.d.ts.map +1 -1
  157. package/dist/pipeline/verification.js +10 -7
  158. package/dist/providers/llm-client.d.ts +38 -0
  159. package/dist/providers/llm-client.d.ts.map +1 -0
  160. package/dist/providers/llm-client.js +105 -0
  161. package/dist/sdk.d.ts +1 -0
  162. package/dist/sdk.d.ts.map +1 -1
  163. package/dist/sdk.js +1 -0
  164. package/dist/session/durable-session-supervisor.d.ts.map +1 -1
  165. package/dist/session/durable-session-supervisor.js +13 -6
  166. package/dist/session/session-persistence.d.ts +29 -0
  167. package/dist/session/session-persistence.d.ts.map +1 -0
  168. package/dist/session/session-persistence.js +45 -0
  169. package/dist/session/session-scheduler.d.ts +8 -1
  170. package/dist/session/session-scheduler.d.ts.map +1 -1
  171. package/dist/session/session-scheduler.js +33 -4
  172. package/dist/session-task-report.d.ts +1 -0
  173. package/dist/session-task-report.d.ts.map +1 -1
  174. package/dist/session-task-report.js +2 -1
  175. package/dist/shortcuts.d.ts.map +1 -1
  176. package/dist/shortcuts.js +1 -0
  177. package/dist/skills/registry.d.ts.map +1 -1
  178. package/dist/skills/registry.js +10 -0
  179. package/dist/skills/security-scan.d.ts +15 -0
  180. package/dist/skills/security-scan.d.ts.map +1 -0
  181. package/dist/skills/security-scan.js +95 -0
  182. package/dist/skills/types.d.ts +2 -0
  183. package/dist/skills/types.d.ts.map +1 -1
  184. package/dist/statusline.d.ts +3 -3
  185. package/dist/statusline.d.ts.map +1 -1
  186. package/dist/statusline.js +9 -17
  187. package/dist/token-usage.d.ts +27 -0
  188. package/dist/token-usage.d.ts.map +1 -0
  189. package/dist/token-usage.js +107 -0
  190. package/dist/tools/bash.d.ts.map +1 -1
  191. package/dist/tools/bash.js +5 -3
  192. package/dist/tools/browser-act-session.d.ts +48 -0
  193. package/dist/tools/browser-act-session.d.ts.map +1 -0
  194. package/dist/tools/browser-act-session.js +168 -0
  195. package/dist/tools/browser-act.d.ts +14 -0
  196. package/dist/tools/browser-act.d.ts.map +1 -0
  197. package/dist/tools/browser-act.js +297 -0
  198. package/dist/tools/code-symbols.d.ts +35 -0
  199. package/dist/tools/code-symbols.d.ts.map +1 -0
  200. package/dist/tools/code-symbols.js +257 -0
  201. package/dist/tools/index.d.ts +4 -1
  202. package/dist/tools/index.d.ts.map +1 -1
  203. package/dist/tools/index.js +22 -2
  204. package/dist/tools/lsp.d.ts +13 -0
  205. package/dist/tools/lsp.d.ts.map +1 -0
  206. package/dist/tools/lsp.js +136 -0
  207. package/dist/tools/patch.d.ts +5 -0
  208. package/dist/tools/patch.d.ts.map +1 -1
  209. package/dist/tools/patch.js +25 -4
  210. package/dist/tools/search.d.ts +7 -0
  211. package/dist/tools/search.d.ts.map +1 -1
  212. package/dist/tools/search.js +20 -3
  213. package/dist/tools/skill.d.ts +2 -0
  214. package/dist/tools/skill.d.ts.map +1 -1
  215. package/dist/tools/skill.js +47 -14
  216. package/dist/tools/subtask.d.ts +3 -1
  217. package/dist/tools/subtask.d.ts.map +1 -1
  218. package/dist/tools/subtask.js +151 -56
  219. package/dist/tui/shell.d.ts +12 -0
  220. package/dist/tui/shell.d.ts.map +1 -1
  221. package/dist/tui/shell.js +36 -3
  222. package/dist/tui/streaming-repl.d.ts +1 -0
  223. package/dist/tui/streaming-repl.d.ts.map +1 -1
  224. package/dist/tui/streaming-repl.js +27 -4
  225. package/dist/tui/streaming-tui.d.ts +10 -0
  226. package/dist/tui/streaming-tui.d.ts.map +1 -1
  227. package/dist/tui/streaming-tui.js +70 -12
  228. package/dist/types.d.ts +0 -8
  229. package/dist/types.d.ts.map +1 -1
  230. package/dist/workflow-runtime.d.ts.map +1 -1
  231. package/dist/workflow-runtime.js +10 -2
  232. package/dist/workflow-types.d.ts +2 -0
  233. package/dist/workflow-types.d.ts.map +1 -1
  234. package/docs/install.md +46 -12
  235. package/docs/skills.md +56 -7
  236. package/docs/web-routing.md +83 -0
  237. package/package.json +18 -4
  238. package/scripts/dep-layers.mjs +275 -0
  239. package/scripts/eval-llm.mjs +27 -0
  240. package/scripts/eval-recovery.mjs +101 -0
  241. package/scripts/eval-tasks.mjs +20 -0
  242. package/scripts/validate-packaging.mjs +57 -0
  243. package/skills/examples/README.md +23 -0
  244. package/skills/examples/add-function/SKILL.md +31 -0
  245. package/skills/examples/fix-failing-test/SKILL.md +32 -0
  246. package/skills/examples/pr-summary/SKILL.md +34 -0
  247. package/skills/lifecycle-build/SKILL.md +22 -0
  248. package/skills/lifecycle-plan/SKILL.md +32 -0
  249. package/skills/lifecycle-review/SKILL.md +25 -0
  250. package/skills/lifecycle-ship/SKILL.md +22 -0
  251. package/skills/lifecycle-spec/SKILL.md +24 -0
  252. package/skills/lifecycle-test/SKILL.md +21 -0
  253. package/skills/opencli/SKILL.md +222 -0
  254. package/skills/qling.md +14 -3
@@ -1,42 +1,49 @@
1
1
  // ============================================================
2
- // 轻灵 - Agent Loop v2(整合 Pipeline Hook + Section Prompt + Token Budget
2
+ // 轻灵 - Agent Loop v2(整合 Pipeline Hook + Section Prompt)
3
+ // Token 计数仅采用 provider 官方 usage 字段。
3
4
  // ============================================================
4
- import axios from "axios";
5
5
  import * as os from "os";
6
6
  import * as path from "path";
7
7
  import * as fs from "fs/promises";
8
- import { exec } from "child_process";
9
- import { existsSync } from "fs";
10
- import { dispatch, ALL_TOOLS, setMCPRegistry } from "./tools/index.js";
8
+ import { ALL_TOOLS, setMCPRegistry } from "./tools/index.js";
11
9
  import { HookManager, ToolPipeline } from "./pipeline/hooks.js";
12
- import { buildDefaultRegistry, buildSystemPrompt, SECTION_IDS, buildRepoMapSection } from "./pipeline/sections.js";
13
- import { MemoryStore, TokenBudgetManager, extractDreamMemories } from "./memory.js";
10
+ import { buildDefaultRegistry } from "./pipeline/sections.js";
11
+ import { MemoryStore } from "./memory.js";
14
12
  import { WriteAheadLog } from "./memory/wal.js";
15
- import { extractDreamMemoriesLLM } from "./memory/memory-llm-dream.js";
13
+ import { runAutoDream } from "./memory/lifecycle.js";
16
14
  import { MCPRegistry } from "./mcp/registry.js";
17
15
  import { mcpToolsToNativeDefinitions } from "./mcp/bridge.js";
18
- import { ApprovalGate, ApprovalRequiredError } from "./guard/approval.js";
16
+ import { ApprovalGate } from "./guard/approval.js";
19
17
  import { MetricsCollector } from "./metrics/collector.js";
20
18
  import { AgentTelemetry } from "./metrics/agent-telemetry.js";
21
19
  import { VerificationAgent } from "./pipeline/verification.js";
22
20
  import { ContextCompactor } from "./context-compactor.js";
23
21
  import { KnowledgeAgentAdapter } from "./knowledge-agent.js";
24
22
  import { WorkflowRuntime } from "./workflow-runtime.js";
25
- import { checkToolConsistency } from "./pipeline/consistency-checker.js";
26
- import { DashboardServer } from "./dashboard-server.js";
27
23
  import { DiscoveryRegistry } from "./discovery-registry.js";
28
24
  import { MissionManager } from "./mission/manager.js";
29
25
  import { getSkillDirs } from "./tools/skill.js";
30
26
  import { listSkills } from "./skills/registry.js";
31
27
  import { buildSkillsSection } from "./pipeline/sections.js";
32
- import { applyContentFilter, setCustomPatterns } from "./guard/content-filter.js";
33
- import { appendGuardAudit } from "./guard.js";
28
+ import { setCustomPatterns } from "./guard/content-filter.js";
34
29
  import { guardConfigFromEnv } from "./config.js";
35
30
  import { SessionRegistry, } from "./session/session-registry.js";
31
+ import { applySessionSnapshot, buildSessionSnapshot, defaultSessionSaveName, } from "./session/session-persistence.js";
36
32
  import { apiKeyRequiredForEndpoint, isLoopbackEndpoint, } from "./providers/presets.js";
37
- import { maybeAutoCommitAfterWrite } from "./git/auto-commit.js";
33
+ import { LlmHttpClient } from "./providers/llm-client.js";
34
+ import { ExecutionEventBus } from "./execution/event-bus.js";
35
+ import { RecoveryController } from "./execution/recovery-controller.js";
36
+ import { RunTraceStore } from "./execution/run-trace-store.js";
37
+ import { formatRecoveryInstruction, formatRecoveryPause, } from "./execution/recovery-messages.js";
38
+ import { loadVerificationCommand as loadVerificationCommandFile, persistVerificationCommand as persistVerificationCommandFile, runShellCommand, stagesSummary, } from "./execution/verification-loop.js";
39
+ import { assembleSystemPrompt, reflectiveThink, } from "./agent/system-prompt.js";
40
+ import { runInnerIterationLoop, runOuterAgentLoop, } from "./agent/main-loop.js";
38
41
  const HOME_DIR = os.homedir();
39
42
  const DEFAULT_QLING_DIR = path.join(HOME_DIR, ".qling");
43
+ const EXPLICIT_ENABLE_VALUES = new Set(["1", "true", "on", "yes"]);
44
+ export function resolveMemoryDreamLlmEnabled(env = process.env) {
45
+ return EXPLICIT_ENABLE_VALUES.has(String(env.QLING_MEMORY_DREAM_LLM_ENABLED ?? "").trim().toLowerCase());
46
+ }
40
47
  /** 本地 loopback / ollama 允许空 key,用占位符满足 OpenAI 兼容 Authorization 头 */
41
48
  function resolveSessionApiKey(raw, endpoint, provider) {
42
49
  const trimmed = String(raw ?? "").trim();
@@ -62,7 +69,7 @@ export class AgentEventEmitter {
62
69
  }
63
70
  export class AgentLoop extends AgentEventEmitter {
64
71
  // --- 核心组件 ---
65
- client;
72
+ llmClient;
66
73
  messages = [];
67
74
  config;
68
75
  turnCount = 0;
@@ -74,7 +81,6 @@ export class AgentLoop extends AgentEventEmitter {
74
81
  pipeline;
75
82
  sectionRegistry = buildDefaultRegistry(ALL_TOOLS);
76
83
  memoryStore;
77
- tokenBudget;
78
84
  verifier;
79
85
  compactor;
80
86
  knowledgeAdapter;
@@ -98,6 +104,10 @@ export class AgentLoop extends AgentEventEmitter {
98
104
  sessionRegistry;
99
105
  guardConfig;
100
106
  verificationCommand = null;
107
+ executionEventBus = new ExecutionEventBus();
108
+ recoveryController = new RecoveryController();
109
+ runTraceStore;
110
+ activeRun = null;
101
111
  // --- v0.3 Getters (Management) ---
102
112
  getWorkflowRuntime() { return this.workflowRuntime; }
103
113
  getMemoryStore() { return this.memoryStore; }
@@ -107,6 +117,34 @@ export class AgentLoop extends AgentEventEmitter {
107
117
  getWorkspaceDir() { return this.config.runtime?.workspaceDir ?? process.cwd(); }
108
118
  getMessagesSnapshot() { return this.messages.map((message) => ({ ...message })); }
109
119
  getVerificationCommand() { return this.verificationCommand; }
120
+ getActiveRun() { return this.activeRun ? { ...this.activeRun } : null; }
121
+ getRecoveryState() {
122
+ try {
123
+ return this.recoveryController.getRecoveryState();
124
+ }
125
+ catch {
126
+ return null;
127
+ }
128
+ }
129
+ subscribeExecutionEvents(listener) {
130
+ return this.executionEventBus.subscribe(listener);
131
+ }
132
+ applyRecoveryAction(action) {
133
+ const state = this.recoveryController.applyAction(action);
134
+ if ((action === "cancel" || action === "edit") && this.activeRun) {
135
+ this.executionEventBus.completeRun(this.activeRun.runId, "canceled");
136
+ this.activeRun = null;
137
+ }
138
+ return { state, ...(action === "edit" ? { prompt: state.originalTask } : {}) };
139
+ }
140
+ async listRunTraceIds() { return this.runTraceStore.listRunIds(this.sessionId); }
141
+ async readRunTrace(runId) { return this.runTraceStore.readRun(this.sessionId, runId); }
142
+ async getRecentRunTrace(sessionId = this.sessionId, runId) {
143
+ const target = runId ?? (await this.runTraceStore.listRunIds(sessionId))[0];
144
+ if (!target)
145
+ return [];
146
+ return (await this.runTraceStore.queryRecent(sessionId, target, { limit: 50 })).events;
147
+ }
110
148
  async setVerificationCommand(cmd) {
111
149
  this.verificationCommand = cmd;
112
150
  await this.persistVerificationCommand();
@@ -116,6 +154,8 @@ export class AgentLoop extends AgentEventEmitter {
116
154
  sessionId: this.sessionId,
117
155
  turnCount: this.turnCount,
118
156
  tokens: this.sessionTokens,
157
+ promptTokens: this.sessionPromptTokens,
158
+ completionTokens: this.sessionCompletionTokens,
119
159
  tokenSource: this.tokenUsageSource,
120
160
  compactions: this.compactionCount,
121
161
  };
@@ -142,8 +182,10 @@ export class AgentLoop extends AgentEventEmitter {
142
182
  this.messages = checkpoint.contextSnapshot;
143
183
  }
144
184
  }
145
- // Token 追踪(仅计增量,避免每轮重复计算整个上下文)
185
+ // Token 追踪:仅累加 provider 官方 usage(按请求增量)
146
186
  sessionTokens = 0;
187
+ sessionPromptTokens = 0;
188
+ sessionCompletionTokens = 0;
147
189
  tokenUsageSource = "unknown";
148
190
  initPromise;
149
191
  // 轻量观测指标
@@ -175,6 +217,11 @@ export class AgentLoop extends AgentEventEmitter {
175
217
  this.runtimeRootDir = path.resolve(config.runtime?.fileStateDir ??
176
218
  process.env.QLING_FILE_STATE_DIR ??
177
219
  DEFAULT_QLING_DIR);
220
+ this.runTraceStore = new RunTraceStore({ rootDir: path.join(this.runtimeRootDir, "runs") });
221
+ this.executionEventBus.subscribe((event) => {
222
+ void this.runTraceStore.append(event).catch(() => undefined);
223
+ this.emit("execution", event);
224
+ });
178
225
  this.memoryDir = path.join(this.runtimeRootDir, "memory");
179
226
  this.loggingConfig = {
180
227
  level: config.logging?.level ?? "info",
@@ -191,11 +238,6 @@ export class AgentLoop extends AgentEventEmitter {
191
238
  systemPrompt: config.systemPrompt ?? "",
192
239
  maxIterations: config.maxIterations ?? 50,
193
240
  tools: config.tools ?? ALL_TOOLS,
194
- tokenBudget: config.tokenBudget ?? {
195
- maxTokens: 120_000,
196
- nudgeThreshold: 0.2,
197
- totalBudget: 120_000,
198
- },
199
241
  runtime: {
200
242
  workspaceDir: config.runtime?.workspaceDir ?? process.env.QLING_WORKSPACE_DIR ?? process.cwd(),
201
243
  fileCacheDir: config.runtime?.fileCacheDir ??
@@ -204,7 +246,6 @@ export class AgentLoop extends AgentEventEmitter {
204
246
  fileStateDir: this.runtimeRootDir,
205
247
  maxSteps: config.runtime?.maxSteps ?? 50,
206
248
  parseRetries: config.runtime?.parseRetries ?? 2,
207
- maxTokenBudget: config.runtime?.maxTokenBudget ?? 120_000,
208
249
  toolRepeatLimit: config.runtime?.toolRepeatLimit ?? 6,
209
250
  timeoutMs: config.runtime?.timeoutMs ?? 300_000,
210
251
  },
@@ -223,7 +264,6 @@ export class AgentLoop extends AgentEventEmitter {
223
264
  this.memoryStore = new MemoryStore(this.memoryDir, {
224
265
  workspaceDir: this.config.runtime?.workspaceDir || undefined,
225
266
  });
226
- this.tokenBudget = new TokenBudgetManager(this.config.tokenBudget?.totalBudget ?? 120_000, this.config.tokenBudget?.nudgeThreshold ?? 0.2);
227
267
  this.verifier = new VerificationAgent(apiKey, this.config.model);
228
268
  this.compactor = new ContextCompactor(6000, this.config.model);
229
269
  this.knowledgeAdapter = new KnowledgeAgentAdapter(this.memoryStore);
@@ -244,33 +284,19 @@ export class AgentLoop extends AgentEventEmitter {
244
284
  catch {
245
285
  // ignore parse errors
246
286
  }
247
- this.discoveryRegistry = new DiscoveryRegistry(discoverySources);
248
- this.missionManager = new MissionManager(this.runtimeRootDir);
249
- // HTTP client + retry interceptor
250
- this.client = axios.create({
251
- baseURL: endpoint,
252
- headers: {
253
- Authorization: "Bearer " + this.config.apiKey,
254
- "Content-Type": "application/json",
255
- },
256
- timeout: this.resolveLlmRequestTimeout(),
287
+ this.discoveryRegistry = new DiscoveryRegistry(discoverySources, {
288
+ guardConfig: this.guardConfig,
257
289
  });
258
- this.client.interceptors.response.use((response) => response, async (err) => {
259
- const cfg = err.config;
260
- const maxRetries = 3;
261
- cfg.__retryCount = cfg.__retryCount ?? 0;
262
- // 只在 429 / 500-503 / ECONNRESET / ETIMEDOUT 时重试
263
- const status = err.response?.status;
264
- const shouldRetry = (!err.response || status === 429 || (status >= 500 && status <= 503)) &&
265
- cfg.__retryCount < maxRetries;
266
- if (shouldRetry) {
267
- cfg.__retryCount++;
290
+ this.missionManager = new MissionManager(this.runtimeRootDir);
291
+ // HTTP client(foundation: providers/llm-client)
292
+ this.llmClient = new LlmHttpClient({
293
+ endpoint,
294
+ apiKey: this.config.apiKey,
295
+ timeoutMs: this.resolveLlmRequestTimeout(),
296
+ provider: this.config.provider,
297
+ onRetry: () => {
268
298
  this.retryCountTotal++;
269
- const delay = Math.min(1000 * Math.pow(2, cfg.__retryCount - 1), 10_000);
270
- await new Promise((r) => setTimeout(r, delay));
271
- return this.client(cfg);
272
- }
273
- return Promise.reject(err);
299
+ },
274
300
  });
275
301
  // 初始化系统(存储 promise,在 run() 中 await)
276
302
  this.initPromise = this.init();
@@ -288,9 +314,12 @@ export class AgentLoop extends AgentEventEmitter {
288
314
  console.error("🔍 正在同步动态插件与技能...");
289
315
  await this.discoveryRegistry.syncAll();
290
316
  const discoveredTools = this.discoveryRegistry.getDiscoveredTools();
291
- if (discoveredTools.length > 0) {
292
- this.config.tools = [...this.config.tools, ...discoveredTools];
293
- console.error(`📦 已发现 ${discoveredTools.length} 个动态工具`);
317
+ const executableTools = this.discoveryRegistry.getExecutableTools();
318
+ if (executableTools.length > 0) {
319
+ this.config.tools = [...this.config.tools, ...executableTools];
320
+ }
321
+ if (discoveredTools.length > executableTools.length) {
322
+ console.error(`📦 已发现 ${discoveredTools.length} 个工具定义;未绑定执行器,仅作为本地元数据展示`);
294
323
  }
295
324
  }
296
325
  if (this.loggingConfig.inspectPrompt || this.loggingConfig.inspectRequest) {
@@ -302,7 +331,7 @@ export class AgentLoop extends AgentEventEmitter {
302
331
  const projectionInterval = Number.isFinite(projectionIntervalRaw) && projectionIntervalRaw > 0
303
332
  ? projectionIntervalRaw
304
333
  : 5000;
305
- const dreamLLM = process.env.QLING_MEMORY_DREAM_LLM_ENABLED !== "false";
334
+ const dreamLLM = resolveMemoryDreamLlmEnabled();
306
335
  const dreamThresholdRaw = Number(process.env.QLING_MEMORY_DREAM_TURN_THRESHOLD ?? "24");
307
336
  const dreamThreshold = Number.isFinite(dreamThresholdRaw) && dreamThresholdRaw > 0
308
337
  ? dreamThresholdRaw
@@ -375,19 +404,21 @@ export class AgentLoop extends AgentEventEmitter {
375
404
  if (metricsEnabled) {
376
405
  console.error("[Metrics] enabled, dir=" + metricsDir);
377
406
  }
378
- // v0.3 Dashboard Server
407
+ // v0.3 Dashboard Server — dynamic import keeps agent-runtime free of adapters static edge
379
408
  if (dashboardEnabled) {
380
- this.dashboardServer = new DashboardServer({
381
- port: Number(process.env.QLING_DASHBOARD_PORT) || 9999,
382
- collector: this.metricsCollector,
383
- workflowRuntime: this.workflowRuntime,
384
- agentLoop: this,
385
- });
386
409
  try {
410
+ const { DashboardServer } = await import("./dashboard-server.js");
411
+ this.dashboardServer = new DashboardServer({
412
+ port: Number(process.env.QLING_DASHBOARD_PORT) || 9999,
413
+ collector: this.metricsCollector,
414
+ workflowRuntime: this.workflowRuntime,
415
+ agentLoop: this,
416
+ });
387
417
  await this.dashboardServer.start();
388
418
  }
389
419
  catch (serverErr) {
390
420
  console.warn(`⚠️ Dashboard 启动跳过: ${serverErr.message}`);
421
+ this.dashboardServer = null;
391
422
  }
392
423
  }
393
424
  }
@@ -447,378 +478,100 @@ export class AgentLoop extends AgentEventEmitter {
447
478
  }
448
479
  async run() {
449
480
  await this.initPromise;
450
- const toolSignatureCounts = new Map();
451
- const toolRepeatLimit = Math.max(1, this.config.runtime?.toolRepeatLimit ?? 6);
452
- let selfHealingCount = 0;
453
- for (let i = 0; i < this.config.maxIterations; i++) {
454
- this.turnCount++;
455
- // 保存本轮用户消息(在 assistant/tool 消息 push 之前)
456
- let lastUserMsg = "";
457
- for (let k = this.messages.length - 1; k >= 0; k--) {
458
- if (this.messages[k].role === "user") {
459
- lastUserMsg = this.messages[k].content;
460
- break;
461
- }
462
- }
463
- // 0. Token Budget Nudge
464
- if (this.tokenBudget.shouldNudge()) {
465
- const nudge = this.tokenBudget.buildNudgeMessage();
466
- this.messages.push({ role: "user", content: nudge });
467
- console.error("\n" + nudge + "\n");
468
- }
469
- // 0b. 上下文压缩(超过阈值时触发)
470
- if (this.compactor.needsCompaction(this.messages)) {
471
- console.error("\n📦 上下文压缩中...(" + this.messages.length + " 条消息)");
472
- const compacted = await this.compactor.compact(this.messages);
473
- this.messages = compacted;
474
- this.compactionCount++;
475
- console.error("📦 压缩完成 → " + this.messages.length + " 条消息\n");
476
- }
477
- // 0c. 冲突/注入扫描(Lesson 12: Context Validation)
478
- const conflicts = this.compactor.scanConflicts(this.messages);
479
- if (conflicts.length > 0) {
480
- console.error("⚠️ 检测到 " + conflicts.length + " 处指令冲突");
481
- }
482
- const poison = this.compactor.scanPoison(this.messages);
483
- if (poison.length > 0) {
484
- console.error("🚨 检测到 " + poison.length + " 处可能的提示注入");
485
- }
486
- // 1. 构建 system prompt(动态sections)
487
- const systemPrompt = await this.buildSystemPrompt();
488
- // v0.3 Workflow Checkpoint: Update context
489
- if (process.env.QLING_FEATURES_WORKFLOW_RUNTIME === "true") {
490
- await this.workflowRuntime.updateContext(this.messages);
491
- }
492
- // 2. 准备本地 token fallback;若 provider 返回 usage,优先采用 provider 值。
493
- const lastMsg = this.messages[this.messages.length - 1];
494
- const estimatedRoundTokens = (lastMsg?.content?.length ?? 0) * 4 + (systemPrompt.length * 0.5);
495
- // 3. API 调用
496
- const { content, tool_calls, usage } = await this.chat(systemPrompt);
497
- const tokenUsage = this.resolveRoundTokenUsage(usage, estimatedRoundTokens);
498
- const roundTokens = tokenUsage.tokens;
499
- this.tokenUsageSource = tokenUsage.source;
500
- this.sessionTokens += roundTokens;
501
- this.tokenBudget.addUsage(roundTokens);
502
- this.messages.push({ role: "assistant", content, tool_calls });
503
- this.emit("thinking", content || "正在思考...");
504
- // 4. 无工具调用 结束
505
- if (!tool_calls || tool_calls.length === 0) {
506
- // 知识观察:助手消息
507
- this.knowledgeAdapter.onAssistantMessage(content);
508
- // Auto-dream 检查
509
- await this.checkAutoDream();
510
- // 知识观察:回合结束
511
- await this.knowledgeAdapter.onTurnEnd(this.turnCount);
512
- this.logTurnTelemetry({ turn: this.turnCount, toolCalls: 0, toolFailures: 0 });
513
- return content;
514
- }
515
- // 5. Pipeline 执行(Hook → 工具)
516
- console.error("\n🔧 执行 " + tool_calls.length + " 个工具...\n");
517
- const preparedCalls = [];
518
- for (const tc of tool_calls) {
519
- const parseResult = this.parseToolArguments(tc.function.arguments);
520
- if (!parseResult.ok) {
521
- preparedCalls.push({
522
- call: {
523
- id: tc.id,
524
- name: tc.function.name,
525
- arguments: {},
526
- },
527
- immediateResult: {
528
- tool_call_id: tc.id,
529
- output: `Error: [TOOL_INVALID_ARGUMENTS] ${parseResult.error}`,
530
- is_error: true,
531
- error: {
532
- code: "TOOL_INVALID_ARGUMENTS",
533
- message: parseResult.error,
534
- category: "runtime",
535
- },
536
- },
537
- });
538
- continue;
539
- }
540
- const call = {
541
- id: tc.id,
542
- name: tc.function.name,
543
- arguments: parseResult.value,
544
- };
545
- const signature = this.buildToolSignature(call.name, call.arguments);
546
- const repeatCount = (toolSignatureCounts.get(signature) ?? 0) + 1;
547
- toolSignatureCounts.set(signature, repeatCount);
548
- if (repeatCount > toolRepeatLimit) {
549
- preparedCalls.push({
550
- call,
551
- immediateResult: {
552
- tool_call_id: tc.id,
553
- output: `Error: [TOOL_REPEAT_LIMIT_EXCEEDED] ` +
554
- `tool '${call.name}' exceeded repeat limit (${toolRepeatLimit})`,
555
- is_error: true,
556
- error: {
557
- code: "TOOL_REPEAT_LIMIT_EXCEEDED",
558
- message: `tool '${call.name}' exceeded repeat limit (${toolRepeatLimit})`,
559
- category: "guard",
560
- },
561
- },
562
- });
563
- continue;
564
- }
565
- preparedCalls.push({ call });
566
- }
567
- // v0.3 Workflow Checkpoint: Set pending tools
568
- if (process.env.QLING_FEATURES_WORKFLOW_RUNTIME === "true") {
569
- await this.workflowRuntime.setPendingTools(preparedCalls.map(p => p.call));
570
- }
571
- let turnToolCalls = 0;
572
- let turnToolFailures = 0;
573
- for (let j = 0; j < preparedCalls.length; j++) {
574
- const prepared = preparedCalls[j];
575
- const tc = prepared.call;
576
- turnToolCalls++;
577
- // v0.5 M2: Self-Reflective Loop (Inner Monologue)
578
- // 针对高风险工具进行预演评估
579
- if (tc.name === "write" || tc.name === "bash") {
580
- const reflection = await this.reflectiveThink(tc);
581
- if (reflection.decision === "block") {
582
- console.error(`🚨 [内省阻断] ${reflection.reason}`);
583
- this.messages.push({
584
- role: "tool",
585
- content: JSON.stringify({
586
- tool_call_id: tc.id,
587
- output: `Error: [REFLECTION_BLOCKED] ${reflection.reason}. This action was deemed too risky.`,
588
- is_error: true,
589
- }),
590
- tool_call_id: tc.id,
591
- });
592
- turnToolFailures++;
593
- continue;
594
- }
595
- else if (reflection.decision === "warn") {
596
- console.error(`💭 [内省警告] ${reflection.reason}`);
597
- }
598
- }
599
- // v0.3 Tool Spec Boost: Consistency Check
600
- if (process.env.QLING_FEATURES_TOOL_SPEC_BOOST === "true") {
601
- const def = this.config.tools.find(t => t.name === tc.name);
602
- if (def) {
603
- const check = checkToolConsistency(tc, def);
604
- if (!check.ok) {
605
- console.error(`⚠️ [SpecBoost] 检查到幻觉风险: ${tc.name} - ${check.error}`);
606
- this.messages.push({
607
- role: "tool",
608
- content: JSON.stringify({
609
- tool_call_id: tc.id,
610
- output: `Error: [TOOL_SPEC_VIOLATION] ${check.error}. Please correct your arguments based on the schema and examples.`,
611
- is_error: true,
612
- }),
613
- tool_call_id: tc.id,
614
- });
615
- turnToolFailures++;
616
- continue;
617
- }
618
- if (check.warnings.length > 0) {
619
- console.warn(`[SpecBoost] 潜在警告: ${check.warnings.join("; ")}`);
620
- }
621
- }
622
- }
623
- // 知识观察:工具调用前
624
- this.knowledgeAdapter.onToolCall(tc);
625
- this.emit("tool_start", tc.name, tc.arguments);
626
- let result = prepared.immediateResult ?? {
627
- tool_call_id: tc.id,
628
- output: "",
629
- is_error: false,
630
- };
631
- if (!prepared.immediateResult) {
632
- try {
633
- result = await this.pipeline.execute(tc, (t) => dispatch(t));
634
- }
635
- catch (err) {
636
- if (err instanceof ApprovalRequiredError && this.channel) {
637
- // Approval flow
638
- if (process.env.QLING_FEATURES_WORKFLOW_RUNTIME === "true") {
639
- await this.workflowRuntime.awaitApproval();
640
- }
641
- const approvalResponse = await this.approvalGate.requestApproval({
642
- id: err.toolCallId,
643
- toolName: err.toolName,
644
- arguments: tc.arguments,
645
- reason: err.reasons.join("; "),
646
- timestamp: Date.now(),
647
- }, this.channel);
648
- if (approvalResponse.decision === "allow") {
649
- result = await dispatch(tc);
650
- }
651
- else {
652
- result = {
653
- tool_call_id: tc.id,
654
- output: "[Approval Denied] " + err.reasons.join("; "),
655
- is_error: true,
656
- error: { code: "APPROVAL_DENIED", message: "User denied tool execution", category: "permission" },
657
- };
658
- turnToolFailures++;
659
- }
660
- }
661
- else {
662
- result = {
663
- tool_call_id: tc.id,
664
- output: err.message,
665
- is_error: true,
666
- error: { code: "TOOL_ERROR", message: err.message, category: "runtime" },
667
- };
668
- turnToolFailures++;
669
- }
670
- }
671
- }
672
- else {
673
- turnToolFailures++;
674
- }
675
- // 知识观察:工具结果后
676
- this.knowledgeAdapter.onToolResult(result, tc.name);
677
- this.emit("tool_result", tc.name, result.output, result.is_error ?? false);
678
- // v0.5 M1: Knowledge Graph Linking (Automatic)
679
- if (!result.is_error && (tc.name === "bash" || tc.name === "write" || tc.name === "read")) {
680
- let lastUserMsg = "";
681
- for (let i = this.messages.length - 1; i >= 0; i--) {
682
- if (this.messages[i].role === "user") {
683
- lastUserMsg = this.messages[i].content;
684
- break;
685
- }
686
- }
687
- const taskId = "task_" + Buffer.from(lastUserMsg.slice(0, 10)).toString("hex");
688
- this.memoryStore.link({ id: taskId, type: "task", label: lastUserMsg.slice(0, 50) }, "uses", { id: "tool_" + tc.name, type: "tool", label: tc.name });
689
- const args = tc.arguments;
690
- const targetFile = args.path || args.file || "";
691
- if (targetFile) {
692
- this.memoryStore.link({ id: "tool_" + tc.name, type: "tool", label: tc.name }, tc.name === "read" ? "reads" : "writes", { id: "file_" + Buffer.from(targetFile).toString("hex"), type: "file", label: targetFile });
693
- }
694
- }
695
- // Phase 1.2: 可选 git auto-commit(write/patch 成功且非 dry_run)
696
- if (!result.is_error &&
697
- (tc.name === "write" || tc.name === "patch") &&
698
- !tc.arguments?.dry_run) {
699
- const args = tc.arguments;
700
- const targetFile = String(args.path || args.file || "").trim();
701
- if (targetFile) {
702
- try {
703
- const ac = await maybeAutoCommitAfterWrite({
704
- workspaceDir: this.config.runtime?.workspaceDir || process.cwd(),
705
- filePath: targetFile,
706
- toolName: tc.name,
707
- });
708
- if (ac.mode !== "off") {
709
- result = {
710
- ...result,
711
- output: `${result.output}\n\n[${ac.message}]`,
712
- };
713
- }
714
- }
715
- catch {
716
- // auto-commit 失败不影响工具成功语义
717
- }
718
- }
719
- }
720
- // Guard M2: 内容过滤(工具输出)
721
- if (this.guardConfig.enabled && this.guardConfig.content_filter?.enabled) {
722
- const cf = applyContentFilter(result.output, {
723
- pii: this.guardConfig.content_filter.pii_detection,
724
- injection: this.guardConfig.content_filter.injection_detection,
725
- custom: this.guardConfig.content_filter.custom_patterns.length > 0,
726
- });
727
- if (cf.blocked) {
728
- await appendGuardAudit(this.guardConfig, {
729
- tool: tc.name,
730
- action: "deny",
731
- category: "content_filter",
732
- reason: cf.reason,
733
- });
734
- result = {
735
- ...result,
736
- output: `[内容过滤] ${cf.reason}: ${(cf.matches ?? []).join(", ")}`,
737
- is_error: true,
738
- error: { code: "CONTENT_FILTERED", message: cf.reason ?? "content filtered", category: "guard" },
739
- };
740
- turnToolFailures++;
741
- }
742
- }
743
- // 进度展示
744
- const preview = result.output.split("\n")[0].slice(0, 80);
745
- const icon = result.is_error ? "❌" : "✅";
746
- console.error(icon + " " + tc.name + ": " + preview + (result.output.length > 80 ? "..." : ""));
747
- // v0.3 Workflow Checkpoint: Add result
748
- if (process.env.QLING_FEATURES_WORKFLOW_RUNTIME === "true") {
749
- await this.workflowRuntime.addToolResult(result);
750
- }
751
- this.messages.push({
752
- role: "tool",
753
- content: JSON.stringify(result),
754
- tool_call_id: tc.id,
755
- });
756
- }
757
- // 6. 验证阶段(针对写操作)
758
- const hasWrites = preparedCalls.some((t) => t.call.name === "write" || t.call.name === "patch" || t.call.name === "bash");
759
- if (hasWrites) {
760
- if (this.verificationCommand) {
761
- console.error(`\n🔍 正在运行自动验证: ${this.verificationCommand}`);
762
- const { code, stdout, stderr } = await this.runVerificationCommand(this.verificationCommand);
763
- if (code !== 0) {
764
- if (selfHealingCount < 3) {
765
- selfHealingCount++;
766
- const truncatedStdout = stdout.length > 2000 ? stdout.slice(-2000) : stdout;
767
- const truncatedStderr = stderr.length > 2000 ? stderr.slice(-2000) : stderr;
768
- const errorContent = `【自动验证失败】运行验证命令 \`${this.verificationCommand}\` 失败。\n` +
769
- `[stdout]\n${truncatedStdout}\n` +
770
- `[stderr]\n${truncatedStderr}\n` +
771
- `请分析上述报错信息,并使用 patch 或其它工具修正它。(自愈尝试: ${selfHealingCount}/3)`;
772
- this.messages.push({ role: "user", content: errorContent });
773
- console.error(`❌ 自动验证失败,触发第 ${selfHealingCount} 轮自愈调试...`);
774
- continue;
775
- }
776
- else {
777
- console.error(`❌ 自动自愈达到 3 轮上限,验证命令仍然失败。`);
778
- return `❌ [自动验证失败] 已达到最多 3 轮自愈上限,验证命令仍然失败。\n最后报错信息:\n${stderr || stdout}`;
779
- }
780
- }
781
- else {
782
- console.error("✅ 自动验证成功!");
783
- selfHealingCount = 0; // Reset counter on success
784
- }
785
- }
786
- else {
787
- await this.verifyLastOperation();
788
- }
789
- }
790
- // 7. Auto-dream 检查
791
- await this.checkAutoDream();
792
- // v0.5 M1: Experience Distillation (M1 Core)
793
- if (this.turnCount > 0 && !preparedCalls.some(p => p.immediateResult?.is_error || (p.call.id && this.messages.some(m => m.tool_call_id === p.call.id && JSON.parse(m.content).is_error)))) {
794
- const successfulCmds = preparedCalls
795
- .filter(p => p.call.name === "bash")
796
- .map(p => p.call.arguments.cmd || p.call.arguments.command);
797
- if (successfulCmds.length > 0) {
798
- let lastUserMsg = "";
799
- for (let i = this.messages.length - 1; i >= 0; i--) {
800
- if (this.messages[i].role === "user") {
801
- lastUserMsg = this.messages[i].content;
802
- break;
803
- }
804
- }
805
- this.memoryStore.addPractice(lastUserMsg.slice(0, 100), successfulCmds, preparedCalls.map(p => p.call.arguments.path || p.call.arguments.file).filter(Boolean));
806
- console.error(`✨ [认知] 已将 ${successfulCmds.length} 条成功指令蒸馏为最佳实践`);
807
- }
808
- }
809
- // 7b. 记录对话轮次(更新 conversation memory)
810
- this.memoryStore.addConversationTurn("user", lastUserMsg);
811
- this.memoryStore.addConversationTurn("assistant", content);
812
- // 7c. 知识观察:助手消息 + 回合结束
813
- this.knowledgeAdapter.onAssistantMessage(content);
814
- await this.knowledgeAdapter.onTurnEnd(this.turnCount);
815
- this.logTurnTelemetry({
816
- turn: this.turnCount,
817
- toolCalls: turnToolCalls,
818
- toolFailures: turnToolFailures,
481
+ return runOuterAgentLoop({
482
+ sessionId: this.sessionId,
483
+ activeRun: this.activeRun,
484
+ messages: this.messages,
485
+ executionEventBus: this.executionEventBus,
486
+ recoveryController: this.recoveryController,
487
+ emit: (event, ...args) => this.emit(event, ...args),
488
+ getRecoveryState: () => this.getRecoveryState(),
489
+ formatRecoveryPause: (reason, next) => this.formatRecoveryPause(reason, next),
490
+ applyRecoveryStrategy: (failure, strategy) => this.applyRecoveryStrategy(failure, strategy),
491
+ setActiveRun: (run) => {
492
+ this.activeRun = run;
493
+ },
494
+ executeInner: () => this.executeRunInternal(),
495
+ });
496
+ }
497
+ async executeRunInternal() {
498
+ await this.initPromise;
499
+ const counters = {
500
+ sessionTokens: this.sessionTokens,
501
+ sessionPromptTokens: this.sessionPromptTokens,
502
+ sessionCompletionTokens: this.sessionCompletionTokens,
503
+ tokenUsageSource: this.tokenUsageSource,
504
+ };
505
+ const host = {
506
+ messages: this.messages,
507
+ turnCount: this.turnCount,
508
+ sessionId: this.sessionId,
509
+ maxIterations: this.config.maxIterations,
510
+ toolRepeatLimit: Math.max(1, this.config.runtime?.toolRepeatLimit ?? 6),
511
+ parseRetries: this.config.runtime?.parseRetries ?? 0,
512
+ verificationCommand: this.verificationCommand,
513
+ counters,
514
+ compactionCount: this.compactionCount,
515
+ toolCallTotal: this.toolCallTotal,
516
+ toolFailureTotal: this.toolFailureTotal,
517
+ retryCountTotal: this.retryCountTotal,
518
+ loggingFormat: this.loggingConfig.format,
519
+ activeRunId: this.activeRun?.runId,
520
+ compactor: this.compactor,
521
+ pipeline: this.pipeline,
522
+ tools: this.config.tools,
523
+ guardConfig: this.guardConfig,
524
+ channel: this.channel,
525
+ approvalGate: this.approvalGate,
526
+ knowledgeAdapter: this.knowledgeAdapter,
527
+ memoryStore: this.memoryStore,
528
+ workspaceDir: this.config.runtime?.workspaceDir || process.cwd(),
529
+ workflowRuntime: this.workflowRuntime,
530
+ executionEventBus: this.executionEventBus,
531
+ recoveryController: this.recoveryController,
532
+ verifier: this.verifier,
533
+ buildSystemPrompt: () => this.buildSystemPrompt(),
534
+ chat: (systemPrompt, overrides) => this.chat(systemPrompt, overrides ?? {}),
535
+ emit: (event, ...args) => this.emit(event, ...args),
536
+ runVerificationCommand: (cmd) => this.runVerificationCommand(cmd),
537
+ getRecoveryState: () => this.getRecoveryState(),
538
+ reflectiveThink: (tc) => this.reflectiveThink(tc),
539
+ checkAutoDream: () => this.checkAutoDream(),
540
+ };
541
+ const result = await runInnerIterationLoop(host);
542
+ // write back mutable counters from host
543
+ this.turnCount = host.turnCount;
544
+ this.sessionTokens = host.counters.sessionTokens;
545
+ this.sessionPromptTokens = host.counters.sessionPromptTokens;
546
+ this.sessionCompletionTokens = host.counters.sessionCompletionTokens;
547
+ this.tokenUsageSource = host.counters.tokenUsageSource;
548
+ this.compactionCount = host.compactionCount;
549
+ this.toolCallTotal = host.toolCallTotal;
550
+ this.toolFailureTotal = host.toolFailureTotal;
551
+ return result;
552
+ }
553
+ formatRecoveryPause(reason, next) {
554
+ return formatRecoveryPause({
555
+ reason,
556
+ next,
557
+ state: this.getRecoveryState(),
558
+ verificationStagesSummary: this.getVerificationStagesSummary(),
559
+ });
560
+ }
561
+ async applyRecoveryStrategy(failure, strategy) {
562
+ if (strategy === "compact_context_once") {
563
+ const result = await this.compactSessionNow();
564
+ this.messages.push({
565
+ role: "user",
566
+ content: formatRecoveryInstruction(failure, strategy) +
567
+ `\n上下文压缩结果: before=${result.beforeCount} after=${result.afterCount} changed=${result.changed}`,
819
568
  });
569
+ return;
820
570
  }
821
- return "⚠️ 达到最大迭代次数,任务未完成。";
571
+ this.messages.push({
572
+ role: "user",
573
+ content: formatRecoveryInstruction(failure, strategy),
574
+ });
822
575
  }
823
576
  getModel() {
824
577
  return this.config.model;
@@ -863,6 +616,16 @@ export class AgentLoop extends AgentEventEmitter {
863
616
  if (ensured)
864
617
  this.config.apiKey = ensured;
865
618
  }
619
+ // Keep HTTP client in sync with session endpoint / key
620
+ this.llmClient.reconfigure({
621
+ endpoint: this.config.endpoint ?? "",
622
+ apiKey: this.config.apiKey,
623
+ timeoutMs: this.resolveLlmRequestTimeout(),
624
+ provider: this.config.provider,
625
+ onRetry: () => {
626
+ this.retryCountTotal++;
627
+ },
628
+ });
866
629
  return {
867
630
  provider: this.config.provider ?? "unknown",
868
631
  endpoint: this.config.endpoint ?? "",
@@ -876,8 +639,9 @@ export class AgentLoop extends AgentEventEmitter {
876
639
  this.messages = [];
877
640
  this.turnCount = 0;
878
641
  this.sessionTokens = 0;
642
+ this.sessionPromptTokens = 0;
643
+ this.sessionCompletionTokens = 0;
879
644
  this.tokenUsageSource = "unknown";
880
- this.tokenBudget.reset();
881
645
  this.memoryStore.resetSession();
882
646
  this.sectionRegistry.clearCache();
883
647
  }
@@ -1025,11 +789,11 @@ export class AgentLoop extends AgentEventEmitter {
1025
789
  }
1026
790
  // --- Session Persistence ---
1027
791
  async checkpointSession() {
1028
- return this.sessionRegistry.save(this.buildSessionSnapshot(this.sessionId));
792
+ return this.sessionRegistry.save(this.captureSessionSnapshot(this.sessionId));
1029
793
  }
1030
794
  async saveSession(name) {
1031
- const sessionName = name ?? "session-" + new Date().toISOString().replace(/[:.]/g, "-");
1032
- return this.sessionRegistry.save(this.buildSessionSnapshot(sessionName));
795
+ const sessionName = name ?? defaultSessionSaveName();
796
+ return this.sessionRegistry.save(this.captureSessionSnapshot(sessionName));
1033
797
  }
1034
798
  async loadSession(name) {
1035
799
  return (await this.restoreSession(name)) !== null;
@@ -1046,39 +810,39 @@ export class AgentLoop extends AgentEventEmitter {
1046
810
  if (!snapshot) {
1047
811
  return null;
1048
812
  }
1049
- return this.applySessionSnapshot(snapshot);
813
+ return this.hydrateSessionSnapshot(snapshot);
1050
814
  }
1051
815
  async restoreLatestSession() {
1052
816
  const snapshot = await this.sessionRegistry.loadLatest();
1053
817
  if (!snapshot) {
1054
818
  return null;
1055
819
  }
1056
- return this.applySessionSnapshot(snapshot);
820
+ return this.hydrateSessionSnapshot(snapshot);
1057
821
  }
1058
822
  // --- Private Methods ---
1059
- buildSessionSnapshot(name) {
1060
- return {
1061
- name,
823
+ captureSessionSnapshot(name) {
824
+ return buildSessionSnapshot(name, {
1062
825
  sessionId: this.sessionId,
1063
- workspaceDir: this.getWorkspaceDir(),
1064
- createdAt: this.sessionCreatedAt,
1065
- updatedAt: new Date().toISOString(),
826
+ sessionCreatedAt: this.sessionCreatedAt,
1066
827
  messages: this.getMessagesSnapshot(),
1067
828
  turnCount: this.turnCount,
1068
829
  sessionTokens: this.sessionTokens,
1069
830
  compactionCount: this.compactionCount,
1070
- };
831
+ workspaceDir: this.getWorkspaceDir(),
832
+ });
1071
833
  }
1072
- applySessionSnapshot(snapshot) {
1073
- this.messages = snapshot.messages.map((message) => ({ ...message }));
1074
- this.turnCount = snapshot.turnCount;
1075
- this.sessionTokens = snapshot.sessionTokens;
1076
- this.tokenUsageSource = "unknown";
1077
- this.compactionCount = snapshot.compactionCount;
1078
- this.sessionId = snapshot.sessionId;
1079
- this.sessionCreatedAt = snapshot.createdAt;
834
+ hydrateSessionSnapshot(snapshot) {
835
+ const patch = applySessionSnapshot(snapshot);
836
+ this.messages = patch.messages;
837
+ this.turnCount = patch.turnCount;
838
+ this.sessionTokens = patch.sessionTokens;
839
+ this.sessionPromptTokens = patch.sessionPromptTokens;
840
+ this.sessionCompletionTokens = patch.sessionCompletionTokens;
841
+ this.tokenUsageSource = patch.tokenUsageSource;
842
+ this.compactionCount = patch.compactionCount;
843
+ this.sessionId = patch.sessionId;
844
+ this.sessionCreatedAt = patch.sessionCreatedAt;
1080
845
  this.pipeline.setSessionId(this.sessionId);
1081
- this.tokenBudget.syncUsage(this.sessionTokens);
1082
846
  this.memoryStore.resetSession();
1083
847
  this.sectionRegistry.clearCache();
1084
848
  if (this.config.runtime) {
@@ -1086,323 +850,86 @@ export class AgentLoop extends AgentEventEmitter {
1086
850
  ...this.config,
1087
851
  runtime: {
1088
852
  ...this.config.runtime,
1089
- workspaceDir: snapshot.workspaceDir ?? this.config.runtime.workspaceDir,
853
+ workspaceDir: patch.workspaceDir ?? this.config.runtime.workspaceDir,
1090
854
  },
1091
855
  };
1092
856
  }
1093
- return {
1094
- name: snapshot.name,
1095
- sessionId: snapshot.sessionId,
1096
- workspaceDir: snapshot.workspaceDir,
1097
- createdAt: snapshot.createdAt,
1098
- updatedAt: snapshot.updatedAt,
1099
- turnCount: snapshot.turnCount,
1100
- messageCount: snapshot.messages.length,
1101
- sessionTokens: snapshot.sessionTokens,
1102
- compactionCount: snapshot.compactionCount,
1103
- };
857
+ return patch.summary;
1104
858
  }
1105
859
  /** 自我反思循环 (v0.5 M2) */
1106
860
  async reflectiveThink(tc) {
1107
- const { buildReflectionPrompt } = await import("./pipeline/sections.js");
1108
- const prompt = buildReflectionPrompt(tc.name, tc.arguments);
1109
- try {
1110
- // 执行一次极简的内部调用进行风险评估
1111
- const resp = await this.chat(prompt, { max_tokens: 200, temperature: 0 });
1112
- // 提取 JSON(防止模型输出冗余文字)
1113
- const jsonStr = resp.content.match(/\{[\s\S]*\}/)?.[0] || "{}";
1114
- const analysis = JSON.parse(jsonStr);
1115
- return {
1116
- decision: analysis.decision || "proceed",
1117
- reason: analysis.reason || "评估完成。"
1118
- };
1119
- }
1120
- catch {
1121
- // 降级:启发式检查
1122
- const args = tc.arguments;
1123
- const cmd = (args.cmd || args.command || "").toLowerCase();
1124
- if (cmd.includes("rm ") || cmd.includes("del ")) {
1125
- return { decision: "warn", reason: "启发式拦截:检测到可能的删除操作。" };
1126
- }
1127
- return { decision: "proceed", reason: "启发式通过。" };
1128
- }
861
+ return reflectiveThink(tc, (systemPrompt, overrides) => this.chat(systemPrompt, overrides ?? {}));
1129
862
  }
1130
863
  async buildSystemPrompt() {
1131
- // 加载代码地图并更新 REPOMAP section
1132
- const cognitiveIndex = this.memoryStore?.getCognitiveIndex?.();
1133
- if (cognitiveIndex) {
1134
- try {
1135
- const symbols = cognitiveIndex.getAllSymbols();
1136
- this.sectionRegistry.register(buildRepoMapSection(symbols));
1137
- }
1138
- catch (err) {
1139
- // Ignore
1140
- }
1141
- }
1142
- // 更新 token budget section
1143
- const budgetSec = this.sectionRegistry.get(SECTION_IDS.TOKEN_BUDGET);
1144
- if (budgetSec) {
1145
- const used = this.sessionTokens;
1146
- const max = this.tokenBudget.maxTokens;
1147
- this.sectionRegistry.register({
1148
- ...budgetSec,
1149
- content: "【Token 预算】\n已使用: ~" +
1150
- used.toLocaleString() +
1151
- " tokens\n剩余: ~" +
1152
- (max - used).toLocaleString() +
1153
- " tokens (" +
1154
- Math.round(((max - used) / max) * 100) +
1155
- "%)\n当剩余低于 20% 时,主动精简回复,减少工具调用频率。",
1156
- dynamic: true,
1157
- });
1158
- }
1159
- // 加载记忆 (v0.3 支持语义异步预取)
1160
- let memoryStr = "";
1161
- if (this.messages.length > 0) {
1162
- let lastUserMsg = "";
1163
- for (let i = this.messages.length - 1; i >= 0; i--) {
1164
- if (this.messages[i].role === "user") {
1165
- lastUserMsg = this.messages[i].content;
1166
- break;
1167
- }
1168
- }
1169
- const relevant = await this.memoryStore.getRelevant(lastUserMsg, 10);
1170
- if (relevant.length > 0) {
1171
- memoryStr = relevant.map((e) => "[" + e.source + "] " + e.content).join("\n");
1172
- }
1173
- }
1174
- const sectionPrompt = buildSystemPrompt(this.sectionRegistry, {
1175
- memory: memoryStr || undefined,
864
+ return assembleSystemPrompt({
865
+ baseSystemPrompt: this.config.systemPrompt,
866
+ sectionRegistry: this.sectionRegistry,
867
+ memoryStore: this.memoryStore,
868
+ messages: this.messages,
869
+ provider: this.config.provider,
870
+ endpoint: this.config.endpoint,
871
+ workspaceDir: this.config.runtime?.workspaceDir,
872
+ fileCacheDir: this.config.runtime?.fileCacheDir,
873
+ fileStateDir: this.config.runtime?.fileStateDir,
874
+ runtimeRootDir: this.runtimeRootDir,
1176
875
  });
1177
- const parts = [
1178
- this.config.systemPrompt.trim(),
1179
- this.buildRuntimeMetaSection(),
1180
- sectionPrompt,
1181
- ].filter((p) => p && p.trim().length > 0);
1182
- return parts.join("\n\n");
1183
876
  }
1184
877
  async chat(systemPrompt, overrides = {}) {
1185
- const systemMsg = { role: "system", content: systemPrompt };
1186
- const payload = {
1187
- model: this.config.model,
1188
- messages: [systemMsg, ...this.messages],
1189
- tools: this.config.tools.map((t) => ({
1190
- type: "function",
1191
- function: {
1192
- name: t.name,
1193
- description: t.description,
1194
- parameters: t.parameters,
1195
- },
1196
- })),
1197
- stream: false,
1198
- ...overrides,
1199
- };
1200
878
  await this.maybeDumpInspect("prompt", {
1201
879
  turn: this.turnCount,
1202
880
  model: this.config.model,
1203
881
  runtime: this.config.runtime ?? null,
1204
882
  prompt: systemPrompt,
1205
883
  });
1206
- await this.maybeDumpInspect("request", payload);
1207
- let resp;
1208
- try {
1209
- resp = await this.client.post("/chat/completions", payload);
1210
- }
1211
- catch (err) {
1212
- const e = err;
1213
- const detail = JSON.stringify(e.response?.data ?? {}).slice(0, 500);
1214
- throw new Error(`${this.config.provider} API error: ` + detail);
1215
- }
1216
- const choice = resp.data.choices?.[0];
1217
- if (!choice)
1218
- throw new Error(`${this.config.provider} API error: ` + JSON.stringify(resp.data));
1219
- const msg = choice.message;
1220
- let rawToolCalls;
1221
- if (msg.tool_calls && msg.tool_calls.length > 0) {
1222
- rawToolCalls = msg.tool_calls.map((tc) => ({
1223
- id: tc.id,
1224
- type: "function",
1225
- function: {
1226
- name: tc.function.name,
1227
- arguments: typeof tc.function.arguments === "string"
1228
- ? tc.function.arguments
1229
- : JSON.stringify(tc.function.arguments),
1230
- },
1231
- }));
1232
- }
1233
- return {
1234
- content: msg.content ?? "",
1235
- tool_calls: rawToolCalls,
1236
- usage: this.extractChatUsage(resp.data?.usage),
1237
- };
1238
- }
1239
- extractChatUsage(rawUsage) {
1240
- if (!rawUsage || typeof rawUsage !== "object")
1241
- return undefined;
1242
- const usage = rawUsage;
1243
- const total = Number(usage.total_tokens ?? usage.totalTokens);
1244
- if (!Number.isFinite(total) || total <= 0)
1245
- return undefined;
1246
- return { totalTokens: Math.floor(total) };
1247
- }
1248
- resolveRoundTokenUsage(usage, fallbackTokens) {
1249
- if (usage?.totalTokens && Number.isFinite(usage.totalTokens) && usage.totalTokens > 0) {
1250
- return { tokens: usage.totalTokens, source: "provider" };
1251
- }
1252
- return { tokens: fallbackTokens, source: "estimate" };
884
+ await this.maybeDumpInspect("request", {
885
+ model: this.config.model,
886
+ systemPrompt,
887
+ messageCount: this.messages.length,
888
+ toolCount: this.config.tools.length,
889
+ overrides,
890
+ });
891
+ return this.llmClient.chatCompletions({
892
+ model: this.config.model,
893
+ systemPrompt,
894
+ messages: this.messages,
895
+ tools: this.config.tools,
896
+ overrides,
897
+ });
1253
898
  }
1254
- async verifyLastOperation() {
1255
- // 取最近的 bash/write 结果
1256
- const toolMsgs = this.messages.filter((m) => m.role === "tool");
1257
- if (toolMsgs.length === 0)
1258
- return;
1259
- try {
1260
- const lastResult = JSON.parse(toolMsgs[toolMsgs.length - 1].content);
1261
- const vr = await this.verifier.verify("文件操作/Bash执行", "操作成功完成", lastResult.output);
1262
- const icon = vr.verdict === "PASS" ? "✅" : vr.verdict === "FAIL" ? "❌" : "⚠️";
1263
- console.error(icon + " 验证结果: " + vr.verdict);
1264
- if (vr.verdict !== "PASS") {
1265
- console.error(" 详情: " + vr.details);
1266
- }
1267
- this.emit("verification", vr.verdict, vr.details ?? vr.verdict);
1268
- }
1269
- catch {
1270
- // 忽略验证错误
1271
- }
899
+ /** Exposed for doctor / status: current staged verification plan. */
900
+ getVerificationStagesSummary() {
901
+ return stagesSummary(this.verificationCommand);
1272
902
  }
1273
903
  async persistVerificationCommand() {
1274
- const workspaceDir = this.getWorkspaceDir();
1275
- const filePath = path.join(workspaceDir, ".qling-verify.json");
1276
- try {
1277
- if (this.verificationCommand) {
1278
- await fs.writeFile(filePath, JSON.stringify({ verificationCommand: this.verificationCommand }, null, 2), "utf-8");
1279
- }
1280
- else {
1281
- if (existsSync(filePath)) {
1282
- await fs.unlink(filePath);
1283
- }
1284
- }
1285
- }
1286
- catch (err) {
1287
- console.error("[AgentLoop] Failed to persist verification command: " + err.message);
1288
- }
904
+ await persistVerificationCommandFile(this.getWorkspaceDir(), this.verificationCommand);
1289
905
  }
1290
906
  async loadVerificationCommand() {
1291
- const workspaceDir = this.getWorkspaceDir();
1292
- const filePath = path.join(workspaceDir, ".qling-verify.json");
1293
- if (existsSync(filePath)) {
1294
- try {
1295
- const content = await fs.readFile(filePath, "utf-8");
1296
- const data = JSON.parse(content);
1297
- this.verificationCommand = data.verificationCommand ?? null;
1298
- }
1299
- catch (err) {
1300
- console.error("[AgentLoop] Failed to load verification command: " + err.message);
1301
- }
1302
- }
907
+ this.verificationCommand = await loadVerificationCommandFile(this.getWorkspaceDir());
1303
908
  }
1304
909
  runVerificationCommand(cmd) {
1305
- return new Promise((resolve) => {
1306
- exec(cmd, { cwd: this.getWorkspaceDir() }, (error, stdout, stderr) => {
1307
- const code = error ? (error.code ?? 1) : 0;
1308
- resolve({ code, stdout, stderr });
1309
- });
1310
- });
910
+ return runShellCommand(cmd, this.getWorkspaceDir());
1311
911
  }
1312
912
  async checkAutoDream() {
1313
913
  try {
1314
- const transcript = this.messages
1315
- .filter((m) => m.role === "user" || m.role === "assistant")
1316
- .map((m) => m.content);
1317
- let memories;
1318
- let changedCount = 0;
1319
- if (this.memoryDreamLLMEnabled) {
1320
- memories = await extractDreamMemoriesLLM(transcript, this.turnCount, {
1321
- enabled: true,
1322
- model: this.config.model,
1323
- maxTokens: 300,
1324
- apiKey: this.config.apiKey,
1325
- endpoint: this.config.endpoint ?? "https://api.deepseek.com",
1326
- });
1327
- const { consolidateMemoriesLLM } = await import("./memory/consolidation.js");
1328
- const existing = this.memoryStore.exportPersisted();
1329
- const ops = await consolidateMemoriesLLM(memories, existing, {
1330
- apiKey: this.config.apiKey,
1331
- endpoint: this.config.endpoint ?? "https://api.deepseek.com",
1332
- model: this.config.model,
1333
- });
1334
- this.memoryStore.applyOperations(ops, "workspace");
1335
- changedCount = ops.filter((op) => op.action !== "NOOP").length;
1336
- }
1337
- else {
1338
- memories = await extractDreamMemories({ turnCount: this.turnCount, transcript }, { enabled: true, turnThreshold: this.memoryDreamTurnThreshold, transcriptWindow: 4 });
1339
- const existingContents = new Set(this.memoryStore.exportPersisted().map((e) => e.content));
1340
- const newMems = memories.filter((m) => !existingContents.has(m));
1341
- for (const mem of newMems) {
1342
- this.memoryStore.add(mem, "auto-dream", 0.6);
1343
- }
1344
- changedCount = newMems.length;
1345
- }
914
+ const changedCount = await runAutoDream({
915
+ messages: this.messages,
916
+ turnCount: this.turnCount,
917
+ memoryStore: this.memoryStore,
918
+ memoryDreamLLMEnabled: this.memoryDreamLLMEnabled,
919
+ memoryDreamTurnThreshold: this.memoryDreamTurnThreshold,
920
+ memoryMaxEntries: this.memoryMaxEntries,
921
+ model: this.config.model,
922
+ apiKey: this.config.apiKey,
923
+ endpoint: this.config.endpoint ?? "https://api.deepseek.com",
924
+ });
1346
925
  if (changedCount > 0) {
1347
- this.memoryStore.compactPersisted(this.memoryMaxEntries);
1348
- await this.memoryStore.saveToDisk();
1349
926
  console.error("[AutoDream] " + changedCount + " 项长期记忆已整理并保存");
1350
927
  }
1351
928
  }
1352
- catch (err) {
929
+ catch {
1353
930
  // ignore
1354
931
  }
1355
932
  }
1356
- estimateTokens() {
1357
- // 粗略估算:总字符数 × 4
1358
- const totalChars = this.messages.reduce((sum, m) => sum + m.content.length, 0);
1359
- return totalChars * 4;
1360
- }
1361
- logTurnTelemetry(metrics) {
1362
- this.toolCallTotal += metrics.toolCalls;
1363
- this.toolFailureTotal += metrics.toolFailures;
1364
- const turnFailureRate = metrics.toolCalls === 0 ? 0 : Math.round((metrics.toolFailures / metrics.toolCalls) * 100);
1365
- const totalFailureRate = this.toolCallTotal === 0 ? 0 : Math.round((this.toolFailureTotal / this.toolCallTotal) * 100);
1366
- const text = "📊 [Obs] turn=" +
1367
- metrics.turn +
1368
- " tools=" +
1369
- metrics.toolCalls +
1370
- " turnFailRate=" +
1371
- turnFailureRate +
1372
- "% totalFailRate=" +
1373
- totalFailureRate +
1374
- "% compactions=" +
1375
- this.compactionCount +
1376
- " retries=" +
1377
- this.retryCountTotal;
1378
- if (this.loggingConfig.format === "json") {
1379
- console.error(JSON.stringify({
1380
- type: "observability",
1381
- turn: metrics.turn,
1382
- toolCalls: metrics.toolCalls,
1383
- turnFailureRate,
1384
- totalFailureRate,
1385
- compactions: this.compactionCount,
1386
- retries: this.retryCountTotal,
1387
- }));
1388
- return;
1389
- }
1390
- console.error(text);
1391
- }
1392
- buildRuntimeMetaSection() {
1393
- const runtime = this.config.runtime;
1394
- const workspace = runtime?.workspaceDir ?? "(disabled)";
1395
- const cache = runtime?.fileCacheDir ?? path.join(this.runtimeRootDir, "cache");
1396
- const state = runtime?.fileStateDir ?? this.runtimeRootDir;
1397
- return [
1398
- "【Runtime Meta】",
1399
- `provider=${this.config.provider ?? "default"}`,
1400
- `endpoint=${this.config.endpoint ?? "default"}`,
1401
- `workspace_dir=${workspace}`,
1402
- `file_cache_dir=${cache}`,
1403
- `file_state_dir=${state}`,
1404
- ].join("\n");
1405
- }
1406
933
  async maybeDumpInspect(kind, payload) {
1407
934
  if (kind === "prompt" && !this.loggingConfig.inspectPrompt)
1408
935
  return;
@@ -1423,76 +950,4 @@ export class AgentLoop extends AgentEventEmitter {
1423
950
  }
1424
951
  return this.config.runtime?.timeoutMs ?? 120_000;
1425
952
  }
1426
- parseToolArguments(raw) {
1427
- const retries = Math.max(0, this.config.runtime?.parseRetries ?? 0);
1428
- let candidate = String(raw ?? "");
1429
- let lastError = "invalid arguments";
1430
- for (let attempt = 0; attempt <= retries; attempt++) {
1431
- try {
1432
- const parsed = JSON.parse(candidate);
1433
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1434
- lastError = "arguments must be a JSON object";
1435
- }
1436
- else {
1437
- return { ok: true, value: parsed };
1438
- }
1439
- }
1440
- catch (err) {
1441
- lastError = err instanceof Error ? err.message : String(err);
1442
- }
1443
- if (attempt < retries) {
1444
- candidate = this.repairToolArguments(candidate, attempt);
1445
- }
1446
- }
1447
- return {
1448
- ok: false,
1449
- error: `failed after ${retries + 1} attempt(s): ${lastError}`,
1450
- };
1451
- }
1452
- repairToolArguments(source, attempt) {
1453
- let out = source.trim();
1454
- if (attempt === 0) {
1455
- const fenced = out.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
1456
- if (fenced) {
1457
- out = fenced[1].trim();
1458
- }
1459
- return out;
1460
- }
1461
- if (attempt === 1) {
1462
- return out.replace(/,\s*([}\]])/g, "$1");
1463
- }
1464
- if (attempt === 2) {
1465
- return out
1466
- .replace(/[“”]/g, "\"")
1467
- .replace(/[‘’]/g, "'")
1468
- .replace(/([{,]\s*)'([^']+?)'\s*:/g, '$1"$2":')
1469
- .replace(/:\s*'([^']*?)'(\s*[,}])/g, ': "$1"$2');
1470
- }
1471
- return out;
1472
- }
1473
- buildToolSignature(name, args) {
1474
- return `${name}:${this.stableStringify(args)}`;
1475
- }
1476
- stableStringify(value) {
1477
- const normalize = (input) => {
1478
- if (Array.isArray(input)) {
1479
- return input.map((item) => normalize(item));
1480
- }
1481
- if (input && typeof input === "object") {
1482
- const entries = Object.entries(input).sort(([a], [b]) => a.localeCompare(b));
1483
- const obj = {};
1484
- for (const [k, v] of entries) {
1485
- obj[k] = normalize(v);
1486
- }
1487
- return obj;
1488
- }
1489
- return input;
1490
- };
1491
- try {
1492
- return JSON.stringify(normalize(value));
1493
- }
1494
- catch {
1495
- return "[unstringifiable]";
1496
- }
1497
- }
1498
953
  }