@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failure-classifier.d.ts","sourceRoot":"","sources":["../../src/execution/failure-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEzE,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,qBAAqB,CAgCnG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,CAAC,GAAG,MAAM,CAUpG"}
@@ -0,0 +1,66 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ function errorMessage(error) {
4
+ return error instanceof Error ? error.message : String(error);
5
+ }
6
+ function normalizeMessage(value) {
7
+ return value
8
+ .toLowerCase()
9
+ .replace(/[a-z]:\\[^\s]+/gi, "<path>")
10
+ .replace(/(?:\/[^\s]+)+/g, "<path>")
11
+ .replace(/\d{4}-\d{2}-\d{2}(?:[ t]\d{2}:\d{2}:\d{2})?/g, "<time>")
12
+ .replace(/\b\d+(?:\.\d+)?\b/g, "<n>")
13
+ .replace(/\s+/g, " ")
14
+ .trim();
15
+ }
16
+ export function classifyFailure(error, context = {}) {
17
+ const message = errorMessage(error);
18
+ const lower = message.toLowerCase();
19
+ const status = typeof error === "object" && error !== null && "status" in error
20
+ ? Number(error.status)
21
+ : undefined;
22
+ let category = context.provider ? "provider_terminal" : "tool_execution";
23
+ if (status === 429 || (status !== undefined && status >= 500) || /rate limit|econnreset|etimedout|temporar/.test(lower)) {
24
+ category = "provider_transient";
25
+ }
26
+ else if (/approval[_ ]required|requires approval/.test(lower)) {
27
+ category = "permission_required";
28
+ }
29
+ else if (/permission denied|approval denied|forbidden/.test(lower)) {
30
+ category = "permission_denied";
31
+ }
32
+ else if (/sandbox|outside (?:the )?workspace|path.*denied/.test(lower)) {
33
+ category = "sandbox_denied";
34
+ }
35
+ else if (/invalid (?:tool )?arguments?|schema validation|missing required/.test(lower)) {
36
+ category = "invalid_tool_arguments";
37
+ }
38
+ else if (/command not found|is not recognized|enoent/.test(lower)) {
39
+ category = "tool_not_found";
40
+ }
41
+ else if (/verification|test command|build failed|exited with code/.test(lower)) {
42
+ category = "verification_failed";
43
+ }
44
+ else if (/context (?:window )?(?:exhausted|length)|maximum context/.test(lower)) {
45
+ category = "context_exhausted";
46
+ }
47
+ else if (/repeated action|duplicate tool call/.test(lower)) {
48
+ category = "repeated_action";
49
+ }
50
+ else if (/cancel(?:ed|led)/.test(lower)) {
51
+ category = "user_canceled";
52
+ }
53
+ const classification = { category, message, ...context };
54
+ return { ...classification, fingerprint: createFailureFingerprint(classification) };
55
+ }
56
+ export function createFailureFingerprint(failure) {
57
+ const target = failure.targetPath ? path.basename(failure.targetPath).toLowerCase() : "";
58
+ const stable = [
59
+ failure.category,
60
+ failure.tool ?? "",
61
+ normalizeMessage(failure.message),
62
+ target,
63
+ normalizeMessage(failure.verificationCommand ?? ""),
64
+ ].join("|");
65
+ return createHash("sha256").update(stable).digest("hex").slice(0, 20);
66
+ }
@@ -0,0 +1,3 @@
1
+ import type { ProgressSnapshot } from "./types.js";
2
+ export declare function hasExecutionProgress(before: ProgressSnapshot | undefined, after: ProgressSnapshot): boolean;
3
+ //# sourceMappingURL=progress-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-detector.d.ts","sourceRoot":"","sources":["../../src/execution/progress-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAUnD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAW3G"}
@@ -0,0 +1,23 @@
1
+ function normalizedTests(tests) {
2
+ return [...new Set(tests ?? [])].sort();
3
+ }
4
+ function normalizedFiles(files) {
5
+ return [...new Set(files ?? [])].sort();
6
+ }
7
+ export function hasExecutionProgress(before, after) {
8
+ if (!before)
9
+ return true;
10
+ if (before.diffHash !== after.diffHash)
11
+ return true;
12
+ if ((after.completedTodos ?? 0) > (before.completedTodos ?? 0))
13
+ return true;
14
+ const previousTests = normalizedTests(before.failingTests);
15
+ const currentTests = normalizedTests(after.failingTests);
16
+ if (currentTests.length < previousTests.length)
17
+ return true;
18
+ const previousFiles = normalizedFiles(before.changedFiles);
19
+ const currentFiles = normalizedFiles(after.changedFiles);
20
+ if (previousFiles.join("\0") !== currentFiles.join("\0"))
21
+ return true;
22
+ return after.changed === true && before.changed !== true;
23
+ }
@@ -0,0 +1,24 @@
1
+ import { RecoveryStrategyPlanner } from "./recovery-strategy-planner.js";
2
+ import type { FailureClassification, ProgressSnapshot, RecoveryDecision, RecoveryState } from "./types.js";
3
+ export type RecoveryAction = "retry" | "next" | "edit" | "cancel";
4
+ export declare class RecoveryController {
5
+ private readonly sameFingerprintLimit;
6
+ private readonly strategyAttemptLimit;
7
+ private readonly fingerprintCounts;
8
+ private readonly planner;
9
+ private state;
10
+ constructor(options?: {
11
+ sameFingerprintLimit?: number;
12
+ strategyAttemptLimit?: number;
13
+ planner?: RecoveryStrategyPlanner;
14
+ });
15
+ startRun(input: {
16
+ runId: string;
17
+ sessionId: string;
18
+ originalTask: string;
19
+ }): RecoveryState;
20
+ recordFailure(failure: FailureClassification, progress: ProgressSnapshot): RecoveryDecision;
21
+ getRecoveryState(): RecoveryState;
22
+ applyAction(action: RecoveryAction): RecoveryState;
23
+ }
24
+ //# sourceMappingURL=recovery-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-controller.d.ts","sourceRoot":"","sources":["../../src/execution/recovery-controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3G,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAElE,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,KAAK,CAA4B;gBAE7B,OAAO,GAAE;QAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,uBAAuB,CAAA;KAAO;IAM7H,QAAQ,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa;IAY1F,aAAa,CAAC,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB;IAyC3F,gBAAgB,IAAI,aAAa;IAYjC,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa;CA+BnD"}
@@ -0,0 +1,114 @@
1
+ import { hasExecutionProgress } from "./progress-detector.js";
2
+ import { RecoveryStrategyPlanner } from "./recovery-strategy-planner.js";
3
+ export class RecoveryController {
4
+ sameFingerprintLimit;
5
+ strategyAttemptLimit;
6
+ fingerprintCounts = new Map();
7
+ planner;
8
+ state;
9
+ constructor(options = {}) {
10
+ this.sameFingerprintLimit = options.sameFingerprintLimit ?? 2;
11
+ this.strategyAttemptLimit = options.strategyAttemptLimit ?? 4;
12
+ this.planner = options.planner ?? new RecoveryStrategyPlanner();
13
+ }
14
+ startRun(input) {
15
+ this.fingerprintCounts.clear();
16
+ this.state = {
17
+ ...input,
18
+ status: "running",
19
+ strategyAttempts: 0,
20
+ remainingStrategyAttempts: this.strategyAttemptLimit,
21
+ attemptedStrategies: [],
22
+ };
23
+ return this.getRecoveryState();
24
+ }
25
+ recordFailure(failure, progress) {
26
+ if (!this.state)
27
+ throw new Error("recovery run has not started");
28
+ const fingerprint = failure.fingerprint ?? "unknown";
29
+ const count = (this.fingerprintCounts.get(fingerprint) ?? 0) + 1;
30
+ this.fingerprintCounts.set(fingerprint, count);
31
+ const madeProgress = hasExecutionProgress(this.state.lastProgress, progress);
32
+ const strategyAttempts = this.state.strategyAttempts + 1;
33
+ const remainingStrategyAttempts = Math.max(0, this.strategyAttemptLimit - strategyAttempts);
34
+ const hardStop = ["provider_terminal", "permission_required", "permission_denied", "sandbox_denied", "repeated_action", "user_canceled"].includes(failure.category);
35
+ const attempted = this.state.attemptedStrategies;
36
+ const recommendedStrategy = this.planner.next(failure.category, attempted);
37
+ let decision;
38
+ if (hardStop) {
39
+ decision = { action: "pause", category: failure.category, reason: failure.category, remainingStrategyAttempts };
40
+ }
41
+ else if (!recommendedStrategy) {
42
+ decision = { action: "pause", category: failure.category, reason: "no_recovery_strategy", remainingStrategyAttempts };
43
+ }
44
+ else if (strategyAttempts >= this.strategyAttemptLimit) {
45
+ decision = { action: "pause", category: failure.category, reason: "strategy_budget_exhausted", remainingStrategyAttempts };
46
+ }
47
+ else if (count >= this.sameFingerprintLimit && !madeProgress) {
48
+ decision = { action: "pause", category: "no_progress", reason: "same_failure_without_progress", remainingStrategyAttempts };
49
+ }
50
+ else {
51
+ decision = { action: "recover", category: failure.category, reason: "recovery_available", remainingStrategyAttempts, recommendedStrategy };
52
+ }
53
+ const nextState = {
54
+ ...this.state,
55
+ status: decision.action === "recover" ? "recovering" : "paused",
56
+ strategyAttempts,
57
+ remainingStrategyAttempts,
58
+ lastFailure: failure,
59
+ lastProgress: { ...progress, failingTests: progress.failingTests ? [...progress.failingTests] : undefined },
60
+ };
61
+ if (decision.recommendedStrategy) {
62
+ nextState.currentStrategy = decision.recommendedStrategy;
63
+ nextState.attemptedStrategies = [...attempted, decision.recommendedStrategy];
64
+ }
65
+ this.state = nextState;
66
+ return decision;
67
+ }
68
+ getRecoveryState() {
69
+ if (!this.state)
70
+ throw new Error("recovery run has not started");
71
+ return {
72
+ ...this.state,
73
+ attemptedStrategies: [...this.state.attemptedStrategies],
74
+ lastFailure: this.state.lastFailure ? { ...this.state.lastFailure } : undefined,
75
+ lastProgress: this.state.lastProgress
76
+ ? { ...this.state.lastProgress, failingTests: this.state.lastProgress.failingTests ? [...this.state.lastProgress.failingTests] : undefined }
77
+ : undefined,
78
+ };
79
+ }
80
+ applyAction(action) {
81
+ if (!this.state)
82
+ throw new Error("recovery run has not started");
83
+ if (action === "cancel") {
84
+ this.state = { ...this.state, status: "canceled" };
85
+ return this.getRecoveryState();
86
+ }
87
+ if (this.state.status !== "paused") {
88
+ throw new Error("no active paused recovery task");
89
+ }
90
+ if (action === "edit") {
91
+ this.state = { ...this.state, status: "canceled" };
92
+ }
93
+ else if (action === "next") {
94
+ const category = this.state.lastFailure?.category;
95
+ const strategy = category ? this.planner.next(category, this.state.attemptedStrategies) : undefined;
96
+ this.state = strategy
97
+ ? {
98
+ ...this.state,
99
+ status: "recovering",
100
+ currentStrategy: strategy,
101
+ attemptedStrategies: [...this.state.attemptedStrategies, strategy],
102
+ }
103
+ : { ...this.state, status: "paused" };
104
+ if (!strategy) {
105
+ throw new Error("no remaining recovery strategy");
106
+ }
107
+ }
108
+ else {
109
+ // retry: reuse currentStrategy without consuming another budget slot
110
+ this.state = { ...this.state, status: "recovering" };
111
+ }
112
+ return this.getRecoveryState();
113
+ }
114
+ }
@@ -0,0 +1,24 @@
1
+ import type { RecoveryState } from "./types.js";
2
+ export declare function formatRecoveryInstruction(failure: {
3
+ category: string;
4
+ message: string;
5
+ }, strategy?: string): string;
6
+ export declare function formatRecoveryPause(options: {
7
+ reason: string;
8
+ next: string;
9
+ state: RecoveryState | null;
10
+ verificationStagesSummary: string;
11
+ }): string;
12
+ export declare function buildVerificationFailureUserMessage(options: {
13
+ failedStage?: string;
14
+ failedCommand: string;
15
+ failingTests: string[];
16
+ changedFiles: string[];
17
+ fingerprint?: string;
18
+ attemptedStrategies: string[];
19
+ strategy: string;
20
+ stdout: string;
21
+ stderr: string;
22
+ instructionBody: string;
23
+ }): string;
24
+ //# sourceMappingURL=recovery-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-messages.d.ts","sourceRoot":"","sources":["../../src/execution/recovery-messages.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAiBhD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC9C,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAMR;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,yBAAyB,EAAE,MAAM,CAAC;CACnC,GAAG,MAAM,CAeT;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CAWT"}
@@ -0,0 +1,48 @@
1
+ // ============================================================
2
+ // 恢复文案(从 AgentLoop 抽出)— 纯函数,无 IO
3
+ // ============================================================
4
+ const STRATEGY_INSTRUCTIONS = {
5
+ repair_tool_arguments: "检查工具参数 schema,修正参数后只重试这一次工具调用。",
6
+ return_tool_schema: "不要继续执行工具;说明缺失或非法字段,并给出正确参数示例。",
7
+ inspect_command_environment: "先检查 PATH、package scripts 和当前平台可用命令,再选择一个有证据支持的替代命令。",
8
+ use_supported_command: "改用工作区内已有的 package script 或轻灵已注册工具,不要盲目重复原命令。",
9
+ inspect_tool_error: "分析工具错误的直接原因,只修改当前失败目标。",
10
+ retry_tool_once: "在确认参数和目标未变化后,仅重试当前工具一次。",
11
+ narrow_tool_scope: "缩小工具操作范围到当前失败目标,避免重复无关操作。",
12
+ return_tool_diagnostics: "停止重复调用,返回可验证的诊断信息和下一步建议。",
13
+ targeted_verification_repair: "仅修复当前失败测试集合,不重复无关的全量验证。",
14
+ narrow_verification_scope: "缩小验证范围到受影响文件和失败测试,先取得可观测进展。",
15
+ compact_context_once: "仅执行一次上下文压缩,保留用户原文、工具链和错误证据,然后继续任务。",
16
+ transport_retry: "提供方瞬时错误已退避;不要改变任务目标,直接继续。",
17
+ };
18
+ export function formatRecoveryInstruction(failure, strategy) {
19
+ return (`【定向恢复】失败类别=${failure.category};失败原因=${failure.message}\n` +
20
+ `恢复策略=${strategy ?? "暂无"}\n` +
21
+ (STRATEGY_INSTRUCTIONS[strategy ?? ""] ?? "停止重复动作,基于最新证据选择下一步。"));
22
+ }
23
+ export function formatRecoveryPause(options) {
24
+ const state = options.state;
25
+ const files = state?.lastProgress?.changedFiles?.join(", ") || "-";
26
+ return [
27
+ "执行已暂停",
28
+ `原因: ${options.reason}`,
29
+ `已尝试: ${state?.strategyAttempts ?? 0} 次恢复策略`,
30
+ `当前策略: ${state?.currentStrategy ?? "-"}`,
31
+ `已尝试策略: ${(state?.attemptedStrategies ?? []).join(", ") || "-"}`,
32
+ `当前证据: ${state?.lastFailure?.fingerprint ?? "-"}`,
33
+ `修改文件: ${files}`,
34
+ `验证阶段: ${options.verificationStagesSummary}`,
35
+ `下一步: ${options.next};使用 /recover retry|next|edit|cancel`,
36
+ "边界: 本地摘要轨迹已保存,不包含 prompt 或工具正文。",
37
+ ].join("\n");
38
+ }
39
+ export function buildVerificationFailureUserMessage(options) {
40
+ const files = options.changedFiles.join(", ") || "-";
41
+ return (`【定向验证失败】阶段=${options.failedStage ?? "unknown"} 命令=\`${options.failedCommand}\`\n` +
42
+ `失败测试=${options.failingTests.join(", ") || "unknown"}\n` +
43
+ `修改文件=${files}\n` +
44
+ `指纹=${options.fingerprint ?? "-"}\n` +
45
+ `已尝试策略=${options.attemptedStrategies.join(", ") || "-"}\n` +
46
+ `恢复策略=${options.strategy}\n[stdout]\n${options.stdout}\n[stderr]\n${options.stderr}\n` +
47
+ options.instructionBody);
48
+ }
@@ -0,0 +1,16 @@
1
+ import type { ExecutionEvent } from "./types.js";
2
+ export interface RecoveryMetrics {
3
+ runs: number;
4
+ firstAttemptSuccessRate: number;
5
+ finalSuccessRate: number;
6
+ averageStrategyAttempts: number;
7
+ repeatedFailureRate: number;
8
+ automaticRecoverySuccessRate: number;
9
+ noProgressStops: number;
10
+ /** Runs that entered paused (user intervention surface). */
11
+ pausedRuns: number;
12
+ /** Average ms from first failure to first pause event; 0 when unknown. */
13
+ averageTimeToPauseMs: number;
14
+ }
15
+ export declare function calculateRecoveryMetrics(events: ExecutionEvent[]): RecoveryMetrics;
16
+ //# sourceMappingURL=recovery-metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-metrics.d.ts","sourceRoot":"","sources":["../../src/execution/recovery-metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB,EAAE,MAAM,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,4BAA4B,EAAE,MAAM,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,eAAe,CAmElF"}
@@ -0,0 +1,73 @@
1
+ export function calculateRecoveryMetrics(events) {
2
+ const runs = new Map();
3
+ for (const event of events) {
4
+ const current = runs.get(event.runId) ?? [];
5
+ runs.set(event.runId, [...current, event]);
6
+ }
7
+ let firstAttemptSuccesses = 0;
8
+ let finalSuccesses = 0;
9
+ let strategyAttempts = 0;
10
+ let recoveredSuccesses = 0;
11
+ let runsWithRecovery = 0;
12
+ let repeatedFailures = 0;
13
+ let failures = 0;
14
+ let noProgressStops = 0;
15
+ let pausedRuns = 0;
16
+ let timeToPauseTotal = 0;
17
+ let timeToPauseSamples = 0;
18
+ for (const runEvents of runs.values()) {
19
+ const attempts = runEvents.filter((event) => event.type === "attempt_completed");
20
+ let terminal;
21
+ for (let index = runEvents.length - 1; index >= 0; index--) {
22
+ if (runEvents[index].type === "run_completed") {
23
+ terminal = runEvents[index];
24
+ break;
25
+ }
26
+ }
27
+ const recoveries = runEvents.filter((event) => event.type === "recovery_started" || event.recoveryAction === "recover");
28
+ const fingerprints = new Set();
29
+ if (attempts[0]?.status === "succeeded")
30
+ firstAttemptSuccesses++;
31
+ if (terminal?.status === "succeeded")
32
+ finalSuccesses++;
33
+ strategyAttempts += recoveries.length;
34
+ if (recoveries.length > 0) {
35
+ runsWithRecovery++;
36
+ if (terminal?.status === "succeeded")
37
+ recoveredSuccesses++;
38
+ }
39
+ const pauseEvent = runEvents.find((event) => event.status === "paused");
40
+ if (pauseEvent) {
41
+ pausedRuns++;
42
+ const firstFailure = runEvents.find((event) => event.type === "failure" || event.type === "verification_failed" || event.status === "failed");
43
+ if (firstFailure && pauseEvent.timestamp >= firstFailure.timestamp) {
44
+ timeToPauseTotal += pauseEvent.timestamp - firstFailure.timestamp;
45
+ timeToPauseSamples++;
46
+ }
47
+ }
48
+ for (const event of runEvents.filter((item) => item.type.includes("failure") || item.type === "failure")) {
49
+ failures++;
50
+ if (event.fingerprint && fingerprints.has(event.fingerprint))
51
+ repeatedFailures++;
52
+ if (event.fingerprint)
53
+ fingerprints.add(event.fingerprint);
54
+ if (event.category === "no_progress")
55
+ noProgressStops++;
56
+ }
57
+ }
58
+ const runCount = runs.size;
59
+ return {
60
+ runs: runCount,
61
+ firstAttemptSuccessRate: ratio(firstAttemptSuccesses, runCount),
62
+ finalSuccessRate: ratio(finalSuccesses, runCount),
63
+ averageStrategyAttempts: ratio(strategyAttempts, runCount),
64
+ repeatedFailureRate: ratio(repeatedFailures, failures),
65
+ automaticRecoverySuccessRate: ratio(recoveredSuccesses, runsWithRecovery),
66
+ noProgressStops,
67
+ pausedRuns,
68
+ averageTimeToPauseMs: timeToPauseSamples === 0 ? 0 : Math.round(timeToPauseTotal / timeToPauseSamples),
69
+ };
70
+ }
71
+ function ratio(value, total) {
72
+ return total === 0 ? 0 : Number((value / total).toFixed(4));
73
+ }
@@ -0,0 +1,6 @@
1
+ import type { FailureCategory } from "./types.js";
2
+ export declare class RecoveryStrategyPlanner {
3
+ list(category: FailureCategory): string[];
4
+ next(category: FailureCategory, attempted?: readonly string[]): string | undefined;
5
+ }
6
+ //# sourceMappingURL=recovery-strategy-planner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-strategy-planner.d.ts","sourceRoot":"","sources":["../../src/execution/recovery-strategy-planner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAmBlD,qBAAa,uBAAuB;IAClC,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,EAAE;IAIzC,IAAI,CAAC,QAAQ,EAAE,eAAe,EAAE,SAAS,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,GAAG,SAAS;CAGvF"}
@@ -0,0 +1,24 @@
1
+ /** Deterministic recovery choices. Empty lists are deliberate hard stops. */
2
+ const STRATEGIES = {
3
+ provider_transient: ["transport_retry"],
4
+ provider_terminal: [],
5
+ invalid_tool_arguments: ["repair_tool_arguments", "return_tool_schema"],
6
+ permission_required: [],
7
+ permission_denied: [],
8
+ sandbox_denied: [],
9
+ tool_not_found: ["inspect_command_environment", "use_supported_command"],
10
+ tool_execution: ["inspect_tool_error", "retry_tool_once", "narrow_tool_scope", "return_tool_diagnostics"],
11
+ verification_failed: ["targeted_verification_repair", "narrow_verification_scope"],
12
+ context_exhausted: ["compact_context_once"],
13
+ repeated_action: [],
14
+ no_progress: [],
15
+ user_canceled: [],
16
+ };
17
+ export class RecoveryStrategyPlanner {
18
+ list(category) {
19
+ return [...(STRATEGIES[category] ?? [])];
20
+ }
21
+ next(category, attempted = []) {
22
+ return this.list(category).find((strategy) => !attempted.includes(strategy));
23
+ }
24
+ }
@@ -0,0 +1,29 @@
1
+ import type { ExecutionEvent } from "./types.js";
2
+ export declare class RunTraceStore {
3
+ private readonly rootDir;
4
+ private readonly now;
5
+ private readonly retentionMs;
6
+ private readonly maxBytes;
7
+ private appendCount;
8
+ constructor(options?: {
9
+ rootDir?: string;
10
+ now?: () => number;
11
+ retentionDays?: number;
12
+ maxBytes?: number;
13
+ });
14
+ getRunPath(sessionId: string, runId: string): string;
15
+ append(value: object): Promise<void>;
16
+ readRun(sessionId: string, runId: string): Promise<ExecutionEvent[]>;
17
+ listRunIds(sessionId: string): Promise<string[]>;
18
+ queryRecent(sessionId: string, runId: string, options?: {
19
+ limit?: number;
20
+ maxScanBytes?: number;
21
+ }): Promise<{
22
+ events: ExecutionEvent[];
23
+ scannedBytes: number;
24
+ truncated: boolean;
25
+ }>;
26
+ purge(): Promise<void>;
27
+ private collectFiles;
28
+ }
29
+ //# sourceMappingURL=run-trace-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-trace-store.d.ts","sourceRoot":"","sources":["../../src/execution/run-trace-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAoB,MAAM,YAAY,CAAC;AAOnE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAAK;gBAEZ,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO;IAO7G,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAI9C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBpC,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAUpE,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAchD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QACpH,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IAuBI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAYd,YAAY;CAgB3B"}
@@ -0,0 +1,140 @@
1
+ import { readdir, readFile, mkdir, appendFile, rm, stat, open } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ const EVENT_KEYS = [
5
+ "eventId", "runId", "sessionId", "attemptId", "toolCallId", "type", "timestamp", "stage", "status", "tool",
6
+ "category", "fingerprint", "durationMs", "recoveryAction",
7
+ ];
8
+ export class RunTraceStore {
9
+ rootDir;
10
+ now;
11
+ retentionMs;
12
+ maxBytes;
13
+ appendCount = 0;
14
+ constructor(options = {}) {
15
+ this.rootDir = options.rootDir ?? path.join(os.homedir(), ".qling", "runs");
16
+ this.now = options.now ?? (() => Date.now());
17
+ this.retentionMs = (options.retentionDays ?? 30) * 24 * 60 * 60 * 1000;
18
+ this.maxBytes = options.maxBytes ?? 50 * 1024 * 1024;
19
+ }
20
+ getRunPath(sessionId, runId) {
21
+ return path.join(this.rootDir, safeSegment(sessionId), `${safeSegment(runId)}.jsonl`);
22
+ }
23
+ async append(value) {
24
+ const input = value;
25
+ const sessionId = typeof input.sessionId === "string" ? input.sessionId : "unknown";
26
+ const runId = typeof input.runId === "string" ? input.runId : "unknown";
27
+ const event = {};
28
+ for (const key of EVENT_KEYS) {
29
+ if (input[key] !== undefined)
30
+ event[key] = input[key];
31
+ }
32
+ if (input.progress && typeof input.progress === "object") {
33
+ const value = input.progress;
34
+ event.progress = {
35
+ ...(typeof value.changed === "boolean" ? { changed: value.changed } : {}),
36
+ ...(typeof value.diffHash === "string" ? { diffHash: value.diffHash } : {}),
37
+ ...(Array.isArray(value.failingTests) ? { failingTests: value.failingTests.filter((item) => typeof item === "string") } : {}),
38
+ ...(typeof value.completedTodos === "number" ? { completedTodos: value.completedTodos } : {}),
39
+ };
40
+ }
41
+ event.timestamp ??= this.now();
42
+ const file = this.getRunPath(sessionId, runId);
43
+ await mkdir(path.dirname(file), { recursive: true });
44
+ await appendFile(file, `${JSON.stringify(event)}\n`, "utf8");
45
+ this.appendCount++;
46
+ if (this.appendCount % 100 === 0)
47
+ await this.purge();
48
+ }
49
+ async readRun(sessionId, runId) {
50
+ try {
51
+ const raw = await readFile(this.getRunPath(sessionId, runId), "utf8");
52
+ return raw.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
53
+ }
54
+ catch (error) {
55
+ if (error.code === "ENOENT")
56
+ return [];
57
+ throw error;
58
+ }
59
+ }
60
+ async listRunIds(sessionId) {
61
+ const directory = path.join(this.rootDir, safeSegment(sessionId));
62
+ try {
63
+ const names = await readdir(directory);
64
+ const entries = await Promise.all(names.filter((name) => name.endsWith(".jsonl")).map(async (name) => {
65
+ const filePath = path.join(directory, name);
66
+ return { id: name.slice(0, -6), mtimeMs: (await stat(filePath)).mtimeMs };
67
+ }));
68
+ return entries.sort((a, b) => b.mtimeMs - a.mtimeMs).map((entry) => entry.id);
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ }
74
+ async queryRecent(sessionId, runId, options = {}) {
75
+ const limit = Math.max(1, options.limit ?? 50);
76
+ const maxScanBytes = Math.max(1_024, options.maxScanBytes ?? 1024 * 1024);
77
+ const filePath = this.getRunPath(sessionId, runId);
78
+ let handle;
79
+ try {
80
+ handle = await open(filePath, "r");
81
+ const info = await handle.stat();
82
+ const scannedBytes = Math.min(info.size, maxScanBytes);
83
+ const buffer = Buffer.alloc(scannedBytes);
84
+ await handle.read(buffer, 0, scannedBytes, info.size - scannedBytes);
85
+ let text = buffer.toString("utf8");
86
+ if (scannedBytes < info.size)
87
+ text = text.slice(text.indexOf("\n") + 1);
88
+ const events = text.split(/\r?\n/).filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
89
+ return { events, scannedBytes, truncated: scannedBytes < info.size };
90
+ }
91
+ catch (error) {
92
+ if (error.code === "ENOENT")
93
+ return { events: [], scannedBytes: 0, truncated: false };
94
+ throw error;
95
+ }
96
+ finally {
97
+ await handle?.close();
98
+ }
99
+ }
100
+ async purge() {
101
+ const files = await this.collectFiles();
102
+ const cutoff = this.now() - this.retentionMs;
103
+ let total = files.reduce((sum, file) => sum + file.size, 0);
104
+ for (const file of files.sort((a, b) => a.mtimeMs - b.mtimeMs)) {
105
+ if (file.mtimeMs < cutoff || total > this.maxBytes) {
106
+ await rm(file.path, { force: true });
107
+ total -= file.size;
108
+ }
109
+ }
110
+ }
111
+ async collectFiles() {
112
+ const result = [];
113
+ let sessions = [];
114
+ try {
115
+ sessions = await readdir(this.rootDir);
116
+ }
117
+ catch {
118
+ return result;
119
+ }
120
+ for (const session of sessions) {
121
+ const directory = path.join(this.rootDir, session);
122
+ let files = [];
123
+ try {
124
+ files = await readdir(directory);
125
+ }
126
+ catch {
127
+ continue;
128
+ }
129
+ for (const file of files.filter((name) => name.endsWith(".jsonl"))) {
130
+ const filePath = path.join(directory, file);
131
+ const info = await stat(filePath);
132
+ result.push({ path: filePath, size: info.size, mtimeMs: info.mtimeMs });
133
+ }
134
+ }
135
+ return result;
136
+ }
137
+ }
138
+ function safeSegment(value) {
139
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
140
+ }