@clinebot/core 0.0.36 → 0.0.37

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 (228) hide show
  1. package/dist/ClineCore.d.ts +312 -3
  2. package/dist/ClineCore.d.ts.map +1 -1
  3. package/dist/account/cline-account-service.d.ts.map +1 -1
  4. package/dist/cron/cron-event-ingress.d.ts +38 -0
  5. package/dist/cron/cron-event-ingress.d.ts.map +1 -0
  6. package/dist/cron/cron-materializer.d.ts +36 -0
  7. package/dist/cron/cron-materializer.d.ts.map +1 -0
  8. package/dist/cron/cron-reconciler.d.ts +62 -0
  9. package/dist/cron/cron-reconciler.d.ts.map +1 -0
  10. package/dist/cron/cron-report-writer.d.ts +41 -0
  11. package/dist/cron/cron-report-writer.d.ts.map +1 -0
  12. package/dist/cron/cron-runner.d.ts +43 -0
  13. package/dist/cron/cron-runner.d.ts.map +1 -0
  14. package/dist/cron/cron-schema.d.ts +3 -0
  15. package/dist/cron/cron-schema.d.ts.map +1 -0
  16. package/dist/cron/cron-service.d.ts +57 -0
  17. package/dist/cron/cron-service.d.ts.map +1 -0
  18. package/dist/cron/cron-spec-parser.d.ts +27 -0
  19. package/dist/cron/cron-spec-parser.d.ts.map +1 -0
  20. package/dist/cron/cron-watcher.d.ts +23 -0
  21. package/dist/cron/cron-watcher.d.ts.map +1 -0
  22. package/dist/cron/scheduler.d.ts +3 -1
  23. package/dist/cron/scheduler.d.ts.map +1 -1
  24. package/dist/cron/sqlite-cron-store.d.ts +230 -0
  25. package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
  26. package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
  27. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
  28. package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
  29. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
  30. package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
  31. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
  32. package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
  33. package/dist/extensions/tools/constants.d.ts +1 -0
  34. package/dist/extensions/tools/constants.d.ts.map +1 -1
  35. package/dist/extensions/tools/definitions.d.ts +2 -3
  36. package/dist/extensions/tools/definitions.d.ts.map +1 -1
  37. package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
  38. package/dist/extensions/tools/helpers.d.ts +1 -0
  39. package/dist/extensions/tools/helpers.d.ts.map +1 -1
  40. package/dist/extensions/tools/index.d.ts +1 -2
  41. package/dist/extensions/tools/index.d.ts.map +1 -1
  42. package/dist/extensions/tools/presets.d.ts +1 -1
  43. package/dist/extensions/tools/schemas.d.ts +25 -3
  44. package/dist/extensions/tools/schemas.d.ts.map +1 -1
  45. package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
  46. package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
  47. package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
  48. package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
  49. package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
  50. package/dist/extensions/tools/types.d.ts +0 -5
  51. package/dist/extensions/tools/types.d.ts.map +1 -1
  52. package/dist/hooks/hook-bridge.d.ts +118 -0
  53. package/dist/hooks/hook-bridge.d.ts.map +1 -0
  54. package/dist/hooks/hook-file-hooks.d.ts +2 -1
  55. package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
  56. package/dist/hooks/hook-registry.d.ts +16 -0
  57. package/dist/hooks/hook-registry.d.ts.map +1 -0
  58. package/dist/hub/browser-websocket.d.ts.map +1 -1
  59. package/dist/hub/client.d.ts +7 -1
  60. package/dist/hub/client.d.ts.map +1 -1
  61. package/dist/hub/daemon-entry.js +721 -461
  62. package/dist/hub/daemon.d.ts.map +1 -1
  63. package/dist/hub/defaults.d.ts +8 -4
  64. package/dist/hub/defaults.d.ts.map +1 -1
  65. package/dist/hub/index.js +665 -415
  66. package/dist/hub/runtime-handlers.d.ts.map +1 -1
  67. package/dist/hub/server.d.ts +18 -0
  68. package/dist/hub/server.d.ts.map +1 -1
  69. package/dist/hub/session-client.d.ts +3 -0
  70. package/dist/hub/session-client.d.ts.map +1 -1
  71. package/dist/hub/start-shared-server.d.ts.map +1 -1
  72. package/dist/hub/ui-client.d.ts +1 -0
  73. package/dist/hub/ui-client.d.ts.map +1 -1
  74. package/dist/index.d.ts +9 -7
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +756 -467
  77. package/dist/llms/cline-recommended-models.d.ts +20 -0
  78. package/dist/llms/cline-recommended-models.d.ts.map +1 -0
  79. package/dist/llms/handler-factory.d.ts +16 -0
  80. package/dist/llms/handler-factory.d.ts.map +1 -0
  81. package/dist/llms/provider-defaults.d.ts.map +1 -1
  82. package/dist/llms/provider-settings.d.ts +45 -2
  83. package/dist/llms/provider-settings.d.ts.map +1 -1
  84. package/dist/llms/runtime-registry.d.ts.map +1 -1
  85. package/dist/runtime/agent-config-adapter.d.ts +148 -0
  86. package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
  87. package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
  88. package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
  89. package/dist/runtime/history.d.ts +6 -0
  90. package/dist/runtime/history.d.ts.map +1 -1
  91. package/dist/runtime/host.d.ts.map +1 -1
  92. package/dist/runtime/loop-detection.d.ts +59 -0
  93. package/dist/runtime/loop-detection.d.ts.map +1 -0
  94. package/dist/runtime/mistake-tracker.d.ts +69 -0
  95. package/dist/runtime/mistake-tracker.d.ts.map +1 -0
  96. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  97. package/dist/runtime/runtime-event-adapter.d.ts +102 -0
  98. package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
  99. package/dist/runtime/runtime-host.d.ts +28 -3
  100. package/dist/runtime/runtime-host.d.ts.map +1 -1
  101. package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
  102. package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
  103. package/dist/runtime/session-runtime.d.ts +16 -3
  104. package/dist/runtime/session-runtime.d.ts.map +1 -1
  105. package/dist/runtime/user-input-builder.d.ts +24 -0
  106. package/dist/runtime/user-input-builder.d.ts.map +1 -0
  107. package/dist/services/index.js +28 -0
  108. package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
  109. package/dist/services/plugin-tools.d.ts.map +1 -1
  110. package/dist/services/providers/local-provider-registry.d.ts +197 -21
  111. package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
  112. package/dist/services/providers/local-provider-service.d.ts +3 -1
  113. package/dist/services/providers/local-provider-service.d.ts.map +1 -1
  114. package/dist/services/session-data.d.ts.map +1 -1
  115. package/dist/services/session-telemetry.d.ts +7 -2
  116. package/dist/services/session-telemetry.d.ts.map +1 -1
  117. package/dist/services/storage/file-team-store.d.ts.map +1 -1
  118. package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  119. package/dist/services/storage/provider-settings-manager.d.ts +1 -0
  120. package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
  121. package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
  122. package/dist/session/conversation-store.d.ts +30 -0
  123. package/dist/session/conversation-store.d.ts.map +1 -0
  124. package/dist/session/message-builder.d.ts +65 -0
  125. package/dist/session/message-builder.d.ts.map +1 -0
  126. package/dist/session/session-manifest.d.ts +1 -1
  127. package/dist/transports/hub.d.ts +14 -3
  128. package/dist/transports/hub.d.ts.map +1 -1
  129. package/dist/transports/local.d.ts +14 -4
  130. package/dist/transports/local.d.ts.map +1 -1
  131. package/dist/transports/remote.d.ts.map +1 -1
  132. package/dist/types/chat-schema.d.ts +5 -5
  133. package/dist/types/config.d.ts +9 -0
  134. package/dist/types/config.d.ts.map +1 -1
  135. package/dist/types/events.d.ts +7 -6
  136. package/dist/types/events.d.ts.map +1 -1
  137. package/dist/types/provider-settings.d.ts +2 -2
  138. package/dist/types/provider-settings.d.ts.map +1 -1
  139. package/dist/types/session.d.ts +5 -2
  140. package/dist/types/session.d.ts.map +1 -1
  141. package/dist/types.d.ts +4 -4
  142. package/dist/types.d.ts.map +1 -1
  143. package/package.json +4 -4
  144. package/src/ClineCore.ts +691 -6
  145. package/src/account/cline-account-service.ts +44 -6
  146. package/src/cron/cron-event-ingress.ts +357 -0
  147. package/src/cron/cron-materializer.ts +97 -0
  148. package/src/cron/cron-reconciler.ts +241 -0
  149. package/src/cron/cron-report-writer.ts +153 -0
  150. package/src/cron/cron-runner.ts +495 -0
  151. package/src/cron/cron-schema.ts +127 -0
  152. package/src/cron/cron-service.ts +163 -0
  153. package/src/cron/cron-spec-parser.ts +489 -0
  154. package/src/cron/cron-watcher.ts +102 -0
  155. package/src/cron/index.ts +10 -0
  156. package/src/cron/scheduler.ts +141 -6
  157. package/src/cron/sqlite-cron-store.ts +1286 -0
  158. package/src/extensions/plugin/plugin-config-loader.ts +21 -1
  159. package/src/extensions/plugin/plugin-loader.ts +25 -9
  160. package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
  161. package/src/extensions/plugin/plugin-sandbox.ts +131 -7
  162. package/src/extensions/tools/constants.ts +2 -0
  163. package/src/extensions/tools/definitions.ts +31 -22
  164. package/src/extensions/tools/executors/editor.ts +4 -3
  165. package/src/extensions/tools/helpers.ts +24 -0
  166. package/src/extensions/tools/index.ts +1 -2
  167. package/src/extensions/tools/presets.ts +1 -1
  168. package/src/extensions/tools/schemas.ts +13 -18
  169. package/src/extensions/tools/team/delegated-agent.ts +8 -3
  170. package/src/extensions/tools/team/multi-agent.ts +135 -19
  171. package/src/extensions/tools/team/team-tools.ts +151 -91
  172. package/src/extensions/tools/types.ts +0 -6
  173. package/src/hooks/hook-bridge.ts +489 -0
  174. package/src/hooks/hook-file-hooks.ts +58 -3
  175. package/src/hooks/hook-registry.ts +257 -0
  176. package/src/hub/browser-websocket.ts +26 -4
  177. package/src/hub/client.ts +72 -13
  178. package/src/hub/daemon-entry.ts +35 -0
  179. package/src/hub/daemon.ts +117 -14
  180. package/src/hub/defaults.ts +39 -12
  181. package/src/hub/runtime-handlers.ts +4 -3
  182. package/src/hub/server.ts +506 -77
  183. package/src/hub/session-client.ts +43 -1
  184. package/src/hub/start-shared-server.ts +3 -0
  185. package/src/hub/ui-client.ts +4 -0
  186. package/src/index.ts +46 -1
  187. package/src/llms/cline-recommended-models.ts +167 -0
  188. package/src/llms/handler-factory.ts +56 -0
  189. package/src/llms/provider-defaults.ts +17 -1
  190. package/src/llms/provider-settings.ts +48 -1
  191. package/src/llms/runtime-registry.ts +1 -0
  192. package/src/runtime/agent-config-adapter.ts +636 -0
  193. package/src/runtime/agent-runtime-config-builder.ts +205 -0
  194. package/src/runtime/error-feedback.ts +142 -0
  195. package/src/runtime/history.ts +137 -0
  196. package/src/runtime/host.ts +22 -0
  197. package/src/runtime/loop-detection.ts +162 -0
  198. package/src/runtime/mistake-tracker.ts +221 -0
  199. package/src/runtime/runtime-builder.ts +61 -5
  200. package/src/runtime/runtime-event-adapter.ts +412 -0
  201. package/src/runtime/runtime-host.ts +45 -1
  202. package/src/runtime/session-runtime-orchestrator.ts +1253 -0
  203. package/src/runtime/session-runtime.ts +16 -2
  204. package/src/runtime/user-input-builder.ts +167 -0
  205. package/src/services/local-runtime-bootstrap.ts +128 -22
  206. package/src/services/plugin-tools.ts +1 -0
  207. package/src/services/providers/local-provider-registry.ts +273 -57
  208. package/src/services/providers/local-provider-service.ts +67 -7
  209. package/src/services/session-data.ts +16 -14
  210. package/src/services/session-telemetry.ts +6 -15
  211. package/src/services/storage/file-team-store.ts +1 -5
  212. package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
  213. package/src/services/storage/provider-settings-manager.ts +16 -1
  214. package/src/services/storage/sqlite-team-store.ts +1 -5
  215. package/src/session/conversation-store.ts +77 -0
  216. package/src/session/message-builder.ts +941 -0
  217. package/src/transports/hub.ts +458 -33
  218. package/src/transports/local.ts +296 -65
  219. package/src/transports/remote.ts +1 -0
  220. package/src/types/config.ts +9 -0
  221. package/src/types/events.ts +8 -6
  222. package/src/types/index.ts +3 -0
  223. package/src/types/provider-settings.ts +8 -1
  224. package/src/types/session.ts +5 -2
  225. package/src/types.ts +15 -1
  226. package/dist/cron/index.d.ts +0 -6
  227. package/dist/cron/index.d.ts.map +0 -1
  228. package/dist/services/telemetry/index.js +0 -28
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Repeated tool-call loop detection.
3
+ *
4
+ * @see PLAN.md §3.1 — helpers moved from `packages/agents/src/context/loop-detection.ts`.
5
+ * @see PLAN.md §3.2.3 — public surface of `LoopDetectionTracker`.
6
+ *
7
+ * The pure helpers (`createLoopDetectionState`, `resetLoopDetectionState`,
8
+ * `toolCallSignature`, `checkRepeatedToolCall`) are ported verbatim. The
9
+ * `LoopDetectionTracker` class is a thin wrapper that owns a
10
+ * `LoopDetectionState` and exposes the `inspect()` / `reset()` surface that
11
+ * `SessionRuntime` installs as a `beforeTool` hook per §3.2.3.
12
+ */
13
+
14
+ import type { LoopDetectionConfig } from "@clinebot/shared";
15
+
16
+ // =============================================================================
17
+ // Pure helpers (verbatim port)
18
+ // =============================================================================
19
+
20
+ export interface LoopDetectionState {
21
+ lastToolName: string;
22
+ lastToolSignature: string;
23
+ consecutiveIdenticalCount: number;
24
+ }
25
+
26
+ export function createLoopDetectionState(): LoopDetectionState {
27
+ return {
28
+ lastToolName: "",
29
+ lastToolSignature: "",
30
+ consecutiveIdenticalCount: 0,
31
+ };
32
+ }
33
+
34
+ export function resetLoopDetectionState(state: LoopDetectionState): void {
35
+ state.lastToolName = "";
36
+ state.lastToolSignature = "";
37
+ state.consecutiveIdenticalCount = 0;
38
+ }
39
+
40
+ function sortKeys(value: unknown): unknown {
41
+ if (value == null || typeof value !== "object") return value;
42
+ if (Array.isArray(value)) return value.map(sortKeys);
43
+ const sorted: Record<string, unknown> = {};
44
+ for (const key of Object.keys(value as Record<string, unknown>).sort()) {
45
+ sorted[key] = sortKeys((value as Record<string, unknown>)[key]);
46
+ }
47
+ return sorted;
48
+ }
49
+
50
+ export function toolCallSignature(input: unknown): string {
51
+ if (input == null) return "null";
52
+ if (typeof input === "string") return input;
53
+ if (typeof input !== "object") return String(input);
54
+ try {
55
+ return JSON.stringify(sortKeys(input));
56
+ } catch {
57
+ return String(input);
58
+ }
59
+ }
60
+
61
+ export interface LoopCheckResult {
62
+ softWarning: boolean;
63
+ hardEscalation: boolean;
64
+ }
65
+
66
+ export function checkRepeatedToolCall(
67
+ state: LoopDetectionState,
68
+ toolName: string,
69
+ signature: string,
70
+ config: LoopDetectionConfig,
71
+ ): LoopCheckResult {
72
+ if (
73
+ toolName === state.lastToolName &&
74
+ signature === state.lastToolSignature
75
+ ) {
76
+ state.consecutiveIdenticalCount++;
77
+ } else {
78
+ state.consecutiveIdenticalCount = 1;
79
+ }
80
+ state.lastToolName = toolName;
81
+ state.lastToolSignature = signature;
82
+
83
+ return {
84
+ softWarning: state.consecutiveIdenticalCount === config.softThreshold,
85
+ hardEscalation: state.consecutiveIdenticalCount >= config.hardThreshold,
86
+ };
87
+ }
88
+
89
+ // =============================================================================
90
+ // Class wrapper (new — per PLAN.md §3.2.3)
91
+ // =============================================================================
92
+
93
+ /**
94
+ * Verdict returned by {@link LoopDetectionTracker.inspect}.
95
+ *
96
+ * - `"ok"` — no repeated call detected.
97
+ * - `"soft"` — soft-warning threshold reached; SessionRuntime may surface a
98
+ * recovery notice but should not block the call.
99
+ * - `"hard"` — hard-escalation threshold reached; SessionRuntime should
100
+ * stop the run with the provided `message`.
101
+ */
102
+ export interface LoopDetectionVerdict {
103
+ kind: "ok" | "soft" | "hard";
104
+ message?: string;
105
+ }
106
+
107
+ /** Minimal call shape the tracker needs; matches `AgentToolCallPart` subset. */
108
+ export interface LoopDetectionCall {
109
+ name: string;
110
+ input: unknown;
111
+ }
112
+
113
+ const DEFAULT_CONFIG: LoopDetectionConfig = {
114
+ softThreshold: 3,
115
+ hardThreshold: 5,
116
+ };
117
+
118
+ /**
119
+ * Per-session repeated-tool-call detector.
120
+ *
121
+ * `SessionRuntime` owns the instance and installs a `beforeTool` hook
122
+ * (see `AgentRuntimeHooks.beforeTool`) that calls `inspect()` to decide
123
+ * whether to return `{ skip, stop, reason }`.
124
+ */
125
+ export class LoopDetectionTracker {
126
+ private readonly config: LoopDetectionConfig;
127
+ private readonly state: LoopDetectionState = createLoopDetectionState();
128
+
129
+ constructor(config?: Partial<LoopDetectionConfig>) {
130
+ this.config = {
131
+ softThreshold: config?.softThreshold ?? DEFAULT_CONFIG.softThreshold,
132
+ hardThreshold: config?.hardThreshold ?? DEFAULT_CONFIG.hardThreshold,
133
+ };
134
+ }
135
+
136
+ inspect(call: LoopDetectionCall): LoopDetectionVerdict {
137
+ const signature = toolCallSignature(call.input);
138
+ const result = checkRepeatedToolCall(
139
+ this.state,
140
+ call.name,
141
+ signature,
142
+ this.config,
143
+ );
144
+ if (result.hardEscalation) {
145
+ return {
146
+ kind: "hard",
147
+ message: `Detected ${this.state.consecutiveIdenticalCount} consecutive identical calls to \`${call.name}\`; stopping to avoid a loop.`,
148
+ };
149
+ }
150
+ if (result.softWarning) {
151
+ return {
152
+ kind: "soft",
153
+ message: `Detected ${this.state.consecutiveIdenticalCount} consecutive identical calls to \`${call.name}\`; consider trying a different approach.`,
154
+ };
155
+ }
156
+ return { kind: "ok" };
157
+ }
158
+
159
+ reset(): void {
160
+ resetLoopDetectionState(this.state);
161
+ }
162
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Per-session consecutive-mistake tracker.
3
+ *
4
+ * @see PLAN.md §3.1 — wrapped around `recordMistake` moved from
5
+ * `packages/agents/src/api/error-handling.ts` lines 147–311.
6
+ * @see PLAN.md §3.2.3 — public surface of `MistakeTracker`.
7
+ *
8
+ * The pure procedural `recordMistake(input, deps)` becomes `record(input)`
9
+ * on the class; `consecutiveMistakes` is internal state. Other deps flow
10
+ * through the constructor instead.
11
+ *
12
+ * NOTE: the §3.2.3 constructor shape omits some fields (agentId,
13
+ * conversationId/runId getters, appendRecoveryNotice). They are retained
14
+ * here for log + notice parity per PLAN.md §3.4.3/§3.4.5. Step 8
15
+ * (`impl-runtime-porter`) may refactor once SessionRuntime is wired up.
16
+ */
17
+
18
+ import type {
19
+ AgentEvent,
20
+ BasicLogMetadata,
21
+ ConsecutiveMistakeLimitContext,
22
+ ConsecutiveMistakeLimitDecision,
23
+ } from "@clinebot/shared";
24
+
25
+ /**
26
+ * Legacy-agents-style leveled log function. The sdk-re `BasicLogger`
27
+ * does not carry a level argument (§shared/logging/logger.ts); callers
28
+ * are expected to bridge via `metadata.severity` or dispatch to
29
+ * `debug`/`log`/`error`. `MistakeTracker` accepts a leveled callable
30
+ * here so Step 8 can plug in whichever bridging shape `SessionRuntime`
31
+ * ends up using.
32
+ */
33
+ export type LeveledLog = (
34
+ level: "debug" | "info" | "warn" | "error",
35
+ message: string,
36
+ metadata?: BasicLogMetadata,
37
+ ) => void;
38
+
39
+ export type MistakeReason =
40
+ | "api_error"
41
+ | "invalid_tool_call"
42
+ | "tool_execution_failed";
43
+
44
+ export interface RecordMistakeInput {
45
+ iteration: number;
46
+ reason: MistakeReason;
47
+ details?: string;
48
+ /** When true, jump straight to maxConsecutiveMistakes instead of incrementing by 1. */
49
+ forceAtLimit?: boolean;
50
+ }
51
+
52
+ export type MistakeOutcome =
53
+ | { action: "continue"; guidance?: string }
54
+ | { action: "stop"; message: string; reason?: string };
55
+
56
+ export interface MistakeTrackerOptions {
57
+ readonly maxConsecutiveMistakes: number;
58
+ readonly onLimitReached?: (
59
+ ctx: ConsecutiveMistakeLimitContext,
60
+ ) =>
61
+ | Promise<ConsecutiveMistakeLimitDecision>
62
+ | ConsecutiveMistakeLimitDecision;
63
+ readonly emit: (event: AgentEvent) => void;
64
+ readonly log: LeveledLog;
65
+ readonly agentId: string;
66
+ readonly getConversationId: () => string;
67
+ readonly getActiveRunId: () => string;
68
+ readonly appendRecoveryNotice: (
69
+ message: string,
70
+ reason: MistakeReason,
71
+ ) => void;
72
+ }
73
+
74
+ export class MistakeTracker {
75
+ private consecutiveMistakes = 0;
76
+ private readonly options: MistakeTrackerOptions;
77
+
78
+ constructor(options: MistakeTrackerOptions) {
79
+ this.options = options;
80
+ }
81
+
82
+ async record(input: RecordMistakeInput): Promise<MistakeOutcome> {
83
+ const max = this.options.maxConsecutiveMistakes;
84
+ const next = input.forceAtLimit && max ? max : this.consecutiveMistakes + 1;
85
+ this.consecutiveMistakes = next;
86
+
87
+ const errorMessage =
88
+ input.details?.trim() || `consecutive mistake (${input.reason})`;
89
+ this.options.emit({
90
+ type: "error",
91
+ error: new Error(errorMessage),
92
+ recoverable: true,
93
+ iteration: input.iteration,
94
+ });
95
+ this.options.log("warn", "Recorded consecutive mistake", {
96
+ agentId: this.options.agentId,
97
+ conversationId: this.options.getConversationId(),
98
+ runId: this.options.getActiveRunId(),
99
+ iteration: input.iteration,
100
+ reason: input.reason,
101
+ details: input.details,
102
+ consecutiveMistakes: next,
103
+ maxConsecutiveMistakes: this.options.maxConsecutiveMistakes,
104
+ });
105
+
106
+ if (!max || next < max) {
107
+ return { action: "continue" };
108
+ }
109
+
110
+ const decision = await resolveConsecutiveMistakeDecision(
111
+ {
112
+ iteration: input.iteration,
113
+ consecutiveMistakes: next,
114
+ maxConsecutiveMistakes: max,
115
+ reason: input.reason,
116
+ details: input.details,
117
+ },
118
+ this.options.onLimitReached,
119
+ );
120
+
121
+ if (decision.action === "continue") {
122
+ const guidance = decision.guidance?.trim();
123
+ if (guidance) {
124
+ this.options.appendRecoveryNotice(guidance, input.reason);
125
+ }
126
+ this.consecutiveMistakes = 0;
127
+ return { action: "continue", guidance };
128
+ }
129
+
130
+ return {
131
+ action: "stop",
132
+ reason: decision.reason?.trim() || undefined,
133
+ message: buildMistakeLimitStopMessage({
134
+ iteration: input.iteration,
135
+ consecutiveMistakes: next,
136
+ maxConsecutiveMistakes: max,
137
+ reason: input.reason,
138
+ details: input.details,
139
+ stopReason: decision.reason,
140
+ }),
141
+ };
142
+ }
143
+
144
+ reset(): void {
145
+ this.consecutiveMistakes = 0;
146
+ }
147
+
148
+ get value(): number {
149
+ return this.consecutiveMistakes;
150
+ }
151
+ }
152
+
153
+ // =============================================================================
154
+ // Mistake Limit Stop Message (pure helper — ported verbatim)
155
+ // =============================================================================
156
+
157
+ export function buildMistakeLimitStopMessage(input: {
158
+ iteration: number;
159
+ consecutiveMistakes: number;
160
+ maxConsecutiveMistakes: number;
161
+ reason:
162
+ | "api_error"
163
+ | "invalid_tool_call"
164
+ | "completion_without_submit"
165
+ | "tool_execution_failed";
166
+ details?: string;
167
+ stopReason?: string;
168
+ }): string {
169
+ const parts = [
170
+ `Stopped after ${input.consecutiveMistakes}/${input.maxConsecutiveMistakes} consecutive mistakes (${input.reason}) at iteration ${input.iteration}.`,
171
+ ];
172
+ const details = input.details?.trim();
173
+ if (details) {
174
+ parts.push(`Error: ${details}`);
175
+ }
176
+ const stopReason = input.stopReason?.trim();
177
+ if (stopReason) {
178
+ parts.push(`Decision: ${stopReason}`);
179
+ }
180
+ parts.push(
181
+ "Session state was preserved. Send a new prompt to resume from the latest state.",
182
+ );
183
+ return parts.join(" ");
184
+ }
185
+
186
+ // =============================================================================
187
+ // Consecutive Mistake Decision Resolution (pure helper — ported verbatim)
188
+ // =============================================================================
189
+
190
+ async function resolveConsecutiveMistakeDecision(
191
+ input: ConsecutiveMistakeLimitContext,
192
+ callback?: (
193
+ context: ConsecutiveMistakeLimitContext,
194
+ ) =>
195
+ | Promise<ConsecutiveMistakeLimitDecision>
196
+ | ConsecutiveMistakeLimitDecision,
197
+ ): Promise<ConsecutiveMistakeLimitDecision> {
198
+ if (!callback) {
199
+ return {
200
+ action: "stop",
201
+ reason: `maximum consecutive mistakes reached (${input.maxConsecutiveMistakes})`,
202
+ };
203
+ }
204
+ try {
205
+ return await callback(input);
206
+ } catch (error) {
207
+ return {
208
+ action: "stop",
209
+ reason:
210
+ error instanceof Error
211
+ ? error.message
212
+ : `maximum consecutive mistakes reached (${input.maxConsecutiveMistakes})`,
213
+ };
214
+ }
215
+ }
216
+
217
+ // TODO(PLAN.md Step 8): The `emit` channel currently accepts legacy `AgentEvent`
218
+ // so the "recoverable error" event shape is preserved verbatim. When
219
+ // `SessionRuntime` wires this up in Step 8, consider whether this should
220
+ // emit an `AgentRuntimeEvent` (per the §3.2.3 signature proposal) and let
221
+ // the bridge translate, or keep the direct legacy channel for notice parity.
@@ -1,6 +1,11 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import type { BasicLogger, TeamTeammateSpec, Tool } from "@clinebot/shared";
3
+ import type {
4
+ BasicLogger,
5
+ RuntimeConfigExtensionKind,
6
+ TeamTeammateSpec,
7
+ Tool,
8
+ } from "@clinebot/shared";
4
9
  import { resolveSkillsConfigSearchPaths } from "@clinebot/shared/storage";
5
10
  import { nanoid } from "nanoid";
6
11
  import {
@@ -55,6 +60,19 @@ type SkillsExecutorWithMetadata = SkillsExecutor & {
55
60
  configuredSkills?: SkillsExecutorMetadataItem[];
56
61
  };
57
62
 
63
+ const ALL_CONFIG_EXTENSIONS: readonly RuntimeConfigExtensionKind[] = [
64
+ "rules",
65
+ "skills",
66
+ "plugins",
67
+ ];
68
+
69
+ function hasConfigExtension(
70
+ extensions: ReadonlyArray<RuntimeConfigExtensionKind> | undefined,
71
+ kind: RuntimeConfigExtensionKind,
72
+ ): boolean {
73
+ return new Set(extensions ?? ALL_CONFIG_EXTENSIONS).has(kind);
74
+ }
75
+
58
76
  function isToolEnabledByPolicies(
59
77
  toolName: string,
60
78
  toolPolicies: CoreSessionConfig["toolPolicies"],
@@ -423,6 +441,28 @@ function shutdownTeamRuntime(
423
441
  }
424
442
  }
425
443
 
444
+ function isRuntimeLifecycleShutdownReason(reason: string | undefined): boolean {
445
+ if (reason === undefined) {
446
+ return true;
447
+ }
448
+ switch (reason) {
449
+ case "session_stop":
450
+ case "session_complete":
451
+ case "session_error":
452
+ case "session_manager_dispose":
453
+ case "cli_run_shutdown":
454
+ case "cli_interactive_shutdown":
455
+ case "cli_interactive_startup_cancelled":
456
+ case "provider_change":
457
+ case "acp_shutdown":
458
+ case "hub_server_stop":
459
+ case "vscode_webview_dispose":
460
+ return true;
461
+ default:
462
+ return false;
463
+ }
464
+ }
465
+
426
466
  function normalizeConfig(
427
467
  config: CoreSessionConfig,
428
468
  ): Required<
@@ -485,6 +525,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
485
525
  createSpawnTool,
486
526
  onTeamRestored,
487
527
  userInstructionWatcher: sharedUserInstructionWatcher,
528
+ configExtensions,
488
529
  defaultToolExecutors,
489
530
  } = input;
490
531
  const onTeamEvent = input.onTeamEvent ?? (() => {});
@@ -493,7 +534,8 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
493
534
  const tools: Tool[] = [];
494
535
  const effectiveTeamName = config.teamName?.trim() || createTeamName();
495
536
  const teamStoreKey = config.sessionId?.trim() || effectiveTeamName;
496
- const hasLocalSkills = hasSkillsFiles(config.cwd);
537
+ const skillsEnabled = hasConfigExtension(configExtensions, "skills");
538
+ const hasLocalSkills = skillsEnabled && hasSkillsFiles(config.cwd);
497
539
  let teamToolsRegistered = false;
498
540
  const watcherProvided = Boolean(sharedUserInstructionWatcher);
499
541
  let userInstructionWatcher = sharedUserInstructionWatcher;
@@ -501,7 +543,12 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
501
543
  let skillsExecutor: SkillsExecutorWithMetadata | undefined;
502
544
  let mcpShutdown: (() => Promise<void>) | undefined;
503
545
 
504
- if (!userInstructionWatcher && normalized.enableTools && hasLocalSkills) {
546
+ if (
547
+ !userInstructionWatcher &&
548
+ normalized.enableTools &&
549
+ skillsEnabled &&
550
+ hasLocalSkills
551
+ ) {
505
552
  userInstructionWatcher = createUserInstructionConfigWatcher({
506
553
  skills: { workspacePath: config.cwd },
507
554
  rules: { workspacePath: config.cwd },
@@ -512,6 +559,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
512
559
 
513
560
  if (
514
561
  normalized.enableTools &&
562
+ skillsEnabled &&
515
563
  userInstructionWatcher &&
516
564
  (watcherProvided ||
517
565
  hasLocalSkills ||
@@ -561,6 +609,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
561
609
  }
562
610
  | undefined;
563
611
  let pendingLeadTeamTools: Tool[] = [];
612
+ let restoredStateHydratedIntoRuntime = false;
564
613
  const delegatedAgentConfigProvider = createDelegatedAgentConfigProvider({
565
614
  providerId: config.providerId,
566
615
  modelId: config.modelId,
@@ -616,7 +665,10 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
616
665
  };
617
666
  teammateSpecs.set(spec.agentId, spec);
618
667
  }
619
- if (event.type === "teammate_shutdown") {
668
+ if (
669
+ event.type === "teammate_shutdown" &&
670
+ !isRuntimeLifecycleShutdownReason(event.reason)
671
+ ) {
620
672
  teammateSpecs.delete(event.agentId);
621
673
  }
622
674
  teamStore.handleTeamEvent(teamStoreKey, event);
@@ -630,7 +682,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
630
682
  });
631
683
  if (restoredTeamState) {
632
684
  teamRuntime.hydrateState(restoredTeamState);
633
- teamRuntime.markStaleRunsInterrupted("runtime_recovered");
685
+ restoredStateHydratedIntoRuntime = true;
634
686
  }
635
687
  registryEntry.runtime = teamRuntime;
636
688
  }
@@ -668,6 +720,10 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
668
720
  teammateConfigProvider: delegatedAgentConfigProvider,
669
721
  });
670
722
 
723
+ if (restoredStateHydratedIntoRuntime) {
724
+ teamRuntime.recoverActiveRuns("runtime_recovered");
725
+ }
726
+
671
727
  if (teamBootstrap.restoredFromPersistence) {
672
728
  onTeamRestored?.();
673
729
  }