@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.
- package/dist/ClineCore.d.ts +312 -3
- package/dist/ClineCore.d.ts.map +1 -1
- package/dist/account/cline-account-service.d.ts.map +1 -1
- package/dist/cron/cron-event-ingress.d.ts +38 -0
- package/dist/cron/cron-event-ingress.d.ts.map +1 -0
- package/dist/cron/cron-materializer.d.ts +36 -0
- package/dist/cron/cron-materializer.d.ts.map +1 -0
- package/dist/cron/cron-reconciler.d.ts +62 -0
- package/dist/cron/cron-reconciler.d.ts.map +1 -0
- package/dist/cron/cron-report-writer.d.ts +41 -0
- package/dist/cron/cron-report-writer.d.ts.map +1 -0
- package/dist/cron/cron-runner.d.ts +43 -0
- package/dist/cron/cron-runner.d.ts.map +1 -0
- package/dist/cron/cron-schema.d.ts +3 -0
- package/dist/cron/cron-schema.d.ts.map +1 -0
- package/dist/cron/cron-service.d.ts +57 -0
- package/dist/cron/cron-service.d.ts.map +1 -0
- package/dist/cron/cron-spec-parser.d.ts +27 -0
- package/dist/cron/cron-spec-parser.d.ts.map +1 -0
- package/dist/cron/cron-watcher.d.ts +23 -0
- package/dist/cron/cron-watcher.d.ts.map +1 -0
- package/dist/cron/scheduler.d.ts +3 -1
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/sqlite-cron-store.d.ts +230 -0
- package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
- package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
- package/dist/extensions/tools/constants.d.ts +1 -0
- package/dist/extensions/tools/constants.d.ts.map +1 -1
- package/dist/extensions/tools/definitions.d.ts +2 -3
- package/dist/extensions/tools/definitions.d.ts.map +1 -1
- package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
- package/dist/extensions/tools/helpers.d.ts +1 -0
- package/dist/extensions/tools/helpers.d.ts.map +1 -1
- package/dist/extensions/tools/index.d.ts +1 -2
- package/dist/extensions/tools/index.d.ts.map +1 -1
- package/dist/extensions/tools/presets.d.ts +1 -1
- package/dist/extensions/tools/schemas.d.ts +25 -3
- package/dist/extensions/tools/schemas.d.ts.map +1 -1
- package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
- package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
- package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
- package/dist/extensions/tools/types.d.ts +0 -5
- package/dist/extensions/tools/types.d.ts.map +1 -1
- package/dist/hooks/hook-bridge.d.ts +118 -0
- package/dist/hooks/hook-bridge.d.ts.map +1 -0
- package/dist/hooks/hook-file-hooks.d.ts +2 -1
- package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
- package/dist/hooks/hook-registry.d.ts +16 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hub/browser-websocket.d.ts.map +1 -1
- package/dist/hub/client.d.ts +7 -1
- package/dist/hub/client.d.ts.map +1 -1
- package/dist/hub/daemon-entry.js +721 -461
- package/dist/hub/daemon.d.ts.map +1 -1
- package/dist/hub/defaults.d.ts +8 -4
- package/dist/hub/defaults.d.ts.map +1 -1
- package/dist/hub/index.js +665 -415
- package/dist/hub/runtime-handlers.d.ts.map +1 -1
- package/dist/hub/server.d.ts +18 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/session-client.d.ts +3 -0
- package/dist/hub/session-client.d.ts.map +1 -1
- package/dist/hub/start-shared-server.d.ts.map +1 -1
- package/dist/hub/ui-client.d.ts +1 -0
- package/dist/hub/ui-client.d.ts.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +756 -467
- package/dist/llms/cline-recommended-models.d.ts +20 -0
- package/dist/llms/cline-recommended-models.d.ts.map +1 -0
- package/dist/llms/handler-factory.d.ts +16 -0
- package/dist/llms/handler-factory.d.ts.map +1 -0
- package/dist/llms/provider-defaults.d.ts.map +1 -1
- package/dist/llms/provider-settings.d.ts +45 -2
- package/dist/llms/provider-settings.d.ts.map +1 -1
- package/dist/llms/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/agent-config-adapter.d.ts +148 -0
- package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
- package/dist/runtime/history.d.ts +6 -0
- package/dist/runtime/history.d.ts.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/loop-detection.d.ts +59 -0
- package/dist/runtime/loop-detection.d.ts.map +1 -0
- package/dist/runtime/mistake-tracker.d.ts +69 -0
- package/dist/runtime/mistake-tracker.d.ts.map +1 -0
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/runtime-event-adapter.d.ts +102 -0
- package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
- package/dist/runtime/runtime-host.d.ts +28 -3
- package/dist/runtime/runtime-host.d.ts.map +1 -1
- package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
- package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
- package/dist/runtime/session-runtime.d.ts +16 -3
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/user-input-builder.d.ts +24 -0
- package/dist/runtime/user-input-builder.d.ts.map +1 -0
- package/dist/services/index.js +28 -0
- package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
- package/dist/services/plugin-tools.d.ts.map +1 -1
- package/dist/services/providers/local-provider-registry.d.ts +197 -21
- package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
- package/dist/services/providers/local-provider-service.d.ts +3 -1
- package/dist/services/providers/local-provider-service.d.ts.map +1 -1
- package/dist/services/session-data.d.ts.map +1 -1
- package/dist/services/session-telemetry.d.ts +7 -2
- package/dist/services/session-telemetry.d.ts.map +1 -1
- package/dist/services/storage/file-team-store.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-manager.d.ts +1 -0
- package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
- package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
- package/dist/session/conversation-store.d.ts +30 -0
- package/dist/session/conversation-store.d.ts.map +1 -0
- package/dist/session/message-builder.d.ts +65 -0
- package/dist/session/message-builder.d.ts.map +1 -0
- package/dist/session/session-manifest.d.ts +1 -1
- package/dist/transports/hub.d.ts +14 -3
- package/dist/transports/hub.d.ts.map +1 -1
- package/dist/transports/local.d.ts +14 -4
- package/dist/transports/local.d.ts.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/types/chat-schema.d.ts +5 -5
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/events.d.ts +7 -6
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +2 -2
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types/session.d.ts +5 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ClineCore.ts +691 -6
- package/src/account/cline-account-service.ts +44 -6
- package/src/cron/cron-event-ingress.ts +357 -0
- package/src/cron/cron-materializer.ts +97 -0
- package/src/cron/cron-reconciler.ts +241 -0
- package/src/cron/cron-report-writer.ts +153 -0
- package/src/cron/cron-runner.ts +495 -0
- package/src/cron/cron-schema.ts +127 -0
- package/src/cron/cron-service.ts +163 -0
- package/src/cron/cron-spec-parser.ts +489 -0
- package/src/cron/cron-watcher.ts +102 -0
- package/src/cron/index.ts +10 -0
- package/src/cron/scheduler.ts +141 -6
- package/src/cron/sqlite-cron-store.ts +1286 -0
- package/src/extensions/plugin/plugin-config-loader.ts +21 -1
- package/src/extensions/plugin/plugin-loader.ts +25 -9
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
- package/src/extensions/plugin/plugin-sandbox.ts +131 -7
- package/src/extensions/tools/constants.ts +2 -0
- package/src/extensions/tools/definitions.ts +31 -22
- package/src/extensions/tools/executors/editor.ts +4 -3
- package/src/extensions/tools/helpers.ts +24 -0
- package/src/extensions/tools/index.ts +1 -2
- package/src/extensions/tools/presets.ts +1 -1
- package/src/extensions/tools/schemas.ts +13 -18
- package/src/extensions/tools/team/delegated-agent.ts +8 -3
- package/src/extensions/tools/team/multi-agent.ts +135 -19
- package/src/extensions/tools/team/team-tools.ts +151 -91
- package/src/extensions/tools/types.ts +0 -6
- package/src/hooks/hook-bridge.ts +489 -0
- package/src/hooks/hook-file-hooks.ts +58 -3
- package/src/hooks/hook-registry.ts +257 -0
- package/src/hub/browser-websocket.ts +26 -4
- package/src/hub/client.ts +72 -13
- package/src/hub/daemon-entry.ts +35 -0
- package/src/hub/daemon.ts +117 -14
- package/src/hub/defaults.ts +39 -12
- package/src/hub/runtime-handlers.ts +4 -3
- package/src/hub/server.ts +506 -77
- package/src/hub/session-client.ts +43 -1
- package/src/hub/start-shared-server.ts +3 -0
- package/src/hub/ui-client.ts +4 -0
- package/src/index.ts +46 -1
- package/src/llms/cline-recommended-models.ts +167 -0
- package/src/llms/handler-factory.ts +56 -0
- package/src/llms/provider-defaults.ts +17 -1
- package/src/llms/provider-settings.ts +48 -1
- package/src/llms/runtime-registry.ts +1 -0
- package/src/runtime/agent-config-adapter.ts +636 -0
- package/src/runtime/agent-runtime-config-builder.ts +205 -0
- package/src/runtime/error-feedback.ts +142 -0
- package/src/runtime/history.ts +137 -0
- package/src/runtime/host.ts +22 -0
- package/src/runtime/loop-detection.ts +162 -0
- package/src/runtime/mistake-tracker.ts +221 -0
- package/src/runtime/runtime-builder.ts +61 -5
- package/src/runtime/runtime-event-adapter.ts +412 -0
- package/src/runtime/runtime-host.ts +45 -1
- package/src/runtime/session-runtime-orchestrator.ts +1253 -0
- package/src/runtime/session-runtime.ts +16 -2
- package/src/runtime/user-input-builder.ts +167 -0
- package/src/services/local-runtime-bootstrap.ts +128 -22
- package/src/services/plugin-tools.ts +1 -0
- package/src/services/providers/local-provider-registry.ts +273 -57
- package/src/services/providers/local-provider-service.ts +67 -7
- package/src/services/session-data.ts +16 -14
- package/src/services/session-telemetry.ts +6 -15
- package/src/services/storage/file-team-store.ts +1 -5
- package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
- package/src/services/storage/provider-settings-manager.ts +16 -1
- package/src/services/storage/sqlite-team-store.ts +1 -5
- package/src/session/conversation-store.ts +77 -0
- package/src/session/message-builder.ts +941 -0
- package/src/transports/hub.ts +458 -33
- package/src/transports/local.ts +296 -65
- package/src/transports/remote.ts +1 -0
- package/src/types/config.ts +9 -0
- package/src/types/events.ts +8 -6
- package/src/types/index.ts +3 -0
- package/src/types/provider-settings.ts +8 -1
- package/src/types/session.ts +5 -2
- package/src/types.ts +15 -1
- package/dist/cron/index.d.ts +0 -6
- package/dist/cron/index.d.ts.map +0 -1
- 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 {
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
}
|