@simonepri/refined-antigravity-acp 1.2.1 → 1.2.3
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/src/fixes/cancellation-leak/index.d.ts +11 -4
- package/dist/src/fixes/cancellation-leak/index.d.ts.map +1 -1
- package/dist/src/fixes/cancellation-leak/index.js +86 -48
- package/dist/src/fixes/cancellation-leak/index.js.map +1 -1
- package/dist/src/fixes/index.d.ts +1 -0
- package/dist/src/fixes/index.d.ts.map +1 -1
- package/dist/src/fixes/index.js +3 -0
- package/dist/src/fixes/index.js.map +1 -1
- package/dist/src/fixes/repetitive-tool-loop/index.d.ts +70 -0
- package/dist/src/fixes/repetitive-tool-loop/index.d.ts.map +1 -0
- package/dist/src/fixes/repetitive-tool-loop/index.js +437 -0
- package/dist/src/fixes/repetitive-tool-loop/index.js.map +1 -0
- package/package.json +1 -1
- package/readme.md +1 -0
|
@@ -3,18 +3,25 @@
|
|
|
3
3
|
* When a user cancels an in-flight turn via `session/cancel`, upstream `agy_acp_server`
|
|
4
4
|
* leaks internal Go/Python cancellation error strings (`"context canceledThe request was cancelled by the client."`)
|
|
5
5
|
* directly into assistant message stream chunks before terminating the turn.
|
|
6
|
+
* Additionally, if a subsequent prompt is sent immediately after cancellation before upstream has finished
|
|
7
|
+
* unwinding, raw agy fails or crashes with concurrent receive_steps errors.
|
|
6
8
|
*
|
|
7
9
|
* Solution:
|
|
8
|
-
* Intercepts inbound `session/update` chunks matching upstream cancellation error strings
|
|
9
|
-
*
|
|
10
|
+
* Intercepts inbound `session/update` chunks matching upstream cancellation error strings and drops them,
|
|
11
|
+
* preserves terminal `tool_call_update` status notifications so client UIs complete running tools,
|
|
12
|
+
* waits for upstream's natural cancellation response with a safety timeout fallback, and queues any
|
|
13
|
+
* subsequent outbound prompt until the previous cancellation has completely unwound.
|
|
10
14
|
*/
|
|
11
15
|
import { type AcpFix, type AcpStreamMessage } from "../../core/types.js";
|
|
12
16
|
export declare const CANCELLATION_ERROR_REGEX: RegExp;
|
|
13
17
|
export declare const CONCURRENT_RECEIVE_STEPS_REGEX: RegExp;
|
|
14
|
-
export declare const
|
|
18
|
+
export declare const DEFAULT_CANCELLATION_TIMEOUT_MS = 2500;
|
|
15
19
|
export declare function isCancellationText(text: string): boolean;
|
|
16
20
|
export declare function extractMessageChunkText(msg: AcpStreamMessage): string | null;
|
|
17
|
-
export
|
|
21
|
+
export interface CancellationOptions {
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function createCancellationLeakFix(options?: CancellationOptions): AcpFix;
|
|
18
25
|
export declare const cancellationLeakFix: AcpFix;
|
|
19
26
|
export declare const interruptionCleanupFix: AcpFix;
|
|
20
27
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/fixes/cancellation-leak/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/fixes/cancellation-leak/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAKL,KAAK,MAAM,EACX,KAAK,gBAAgB,EAItB,MAAM,qBAAqB,CAAC;AAG7B,eAAO,MAAM,wBAAwB,QACkD,CAAC;AAExF,eAAO,MAAM,8BAA8B,QACmG,CAAC;AAE/I,eAAO,MAAM,+BAA+B,OAAO,CAAC;AAEpD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASxD;AAiBD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAK5E;AAeD,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6GD,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAkD/E;AAED,eAAO,MAAM,mBAAmB,QAA8B,CAAC;AAC/D,eAAO,MAAM,sBAAsB,QAAsB,CAAC"}
|
|
@@ -3,16 +3,20 @@
|
|
|
3
3
|
* When a user cancels an in-flight turn via `session/cancel`, upstream `agy_acp_server`
|
|
4
4
|
* leaks internal Go/Python cancellation error strings (`"context canceledThe request was cancelled by the client."`)
|
|
5
5
|
* directly into assistant message stream chunks before terminating the turn.
|
|
6
|
+
* Additionally, if a subsequent prompt is sent immediately after cancellation before upstream has finished
|
|
7
|
+
* unwinding, raw agy fails or crashes with concurrent receive_steps errors.
|
|
6
8
|
*
|
|
7
9
|
* Solution:
|
|
8
|
-
* Intercepts inbound `session/update` chunks matching upstream cancellation error strings
|
|
9
|
-
*
|
|
10
|
+
* Intercepts inbound `session/update` chunks matching upstream cancellation error strings and drops them,
|
|
11
|
+
* preserves terminal `tool_call_update` status notifications so client UIs complete running tools,
|
|
12
|
+
* waits for upstream's natural cancellation response with a safety timeout fallback, and queues any
|
|
13
|
+
* subsequent outbound prompt until the previous cancellation has completely unwound.
|
|
10
14
|
*/
|
|
11
15
|
import { ACP_METHODS, SESSION_UPDATES, STOP_REASONS, isMethod, } from "../../core/types.js";
|
|
12
|
-
import { extractSessionId
|
|
16
|
+
import { extractSessionId } from "../../core/session-cache.js";
|
|
13
17
|
export const CANCELLATION_ERROR_REGEX = /^(?:context\s+canceled)?\s*The\s+request\s+was\s+cancelled\s+by\s+the\s+client\.?$/i;
|
|
14
18
|
export const CONCURRENT_RECEIVE_STEPS_REGEX = /^Agent connection was lost and could not be re-established:\s*Concurrent receive_steps\(\) calls are not supported on this connection\.?$/i;
|
|
15
|
-
export const
|
|
19
|
+
export const DEFAULT_CANCELLATION_TIMEOUT_MS = 2500;
|
|
16
20
|
export function isCancellationText(text) {
|
|
17
21
|
const trimmed = text.trim();
|
|
18
22
|
if (!trimmed)
|
|
@@ -46,15 +50,20 @@ export function extractMessageChunkText(msg) {
|
|
|
46
50
|
return null;
|
|
47
51
|
return extractContentText(update.content);
|
|
48
52
|
}
|
|
49
|
-
function handleOutboundPrompt(msg, state) {
|
|
53
|
+
async function handleOutboundPrompt(msg, state) {
|
|
50
54
|
const sessionId = extractSessionId(msg);
|
|
51
55
|
const id = msg.id;
|
|
52
|
-
if (sessionId
|
|
53
|
-
state.
|
|
54
|
-
|
|
56
|
+
if (sessionId) {
|
|
57
|
+
const cancelling = state.cancellingSessions.get(sessionId);
|
|
58
|
+
if (cancelling) {
|
|
59
|
+
await cancelling.promise;
|
|
60
|
+
}
|
|
61
|
+
if (id !== undefined && id !== null) {
|
|
62
|
+
state.activePrompts.set(sessionId, id);
|
|
63
|
+
}
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
|
-
function handleOutboundCancel(msg, context, state) {
|
|
66
|
+
function handleOutboundCancel(msg, context, state, timeoutMs) {
|
|
58
67
|
const sessionId = extractSessionId(msg);
|
|
59
68
|
if (!sessionId)
|
|
60
69
|
return;
|
|
@@ -62,58 +71,85 @@ function handleOutboundCancel(msg, context, state) {
|
|
|
62
71
|
if (promptId === undefined)
|
|
63
72
|
return;
|
|
64
73
|
state.activePrompts.delete(sessionId);
|
|
65
|
-
state.
|
|
66
|
-
state.cancelledPromptIds.add(promptId);
|
|
67
|
-
const alreadySettled = context.session &&
|
|
68
|
-
getFixData(context.session, CANCEL_PROMPT_SETTLED_KEY) === promptId;
|
|
69
|
-
if (alreadySettled)
|
|
74
|
+
if (state.cancellingSessions.has(sessionId))
|
|
70
75
|
return;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
let resolveFn;
|
|
77
|
+
const promise = new Promise((resolve) => {
|
|
78
|
+
resolveFn = resolve;
|
|
79
|
+
});
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
const current = state.cancellingSessions.get(sessionId);
|
|
82
|
+
if (current && current.promptId === promptId) {
|
|
83
|
+
state.cancellingSessions.delete(sessionId);
|
|
84
|
+
state.suppressedLateResponseIds.add(promptId);
|
|
85
|
+
if (context.session) {
|
|
86
|
+
context.session.needsRecycle = true;
|
|
87
|
+
}
|
|
88
|
+
const cancelResponse = {
|
|
89
|
+
jsonrpc: "2.0",
|
|
90
|
+
id: promptId,
|
|
91
|
+
result: { stopReason: STOP_REASONS.CANCELLED },
|
|
92
|
+
};
|
|
93
|
+
context.forwardInbound?.(cancelResponse);
|
|
94
|
+
resolveFn();
|
|
95
|
+
}
|
|
96
|
+
}, timeoutMs);
|
|
97
|
+
timer.unref?.();
|
|
98
|
+
state.cancellingSessions.set(sessionId, {
|
|
99
|
+
promptId,
|
|
100
|
+
timer,
|
|
101
|
+
promise,
|
|
102
|
+
resolve: resolveFn,
|
|
103
|
+
});
|
|
88
104
|
}
|
|
89
|
-
function handleInboundPromptId(msg,
|
|
105
|
+
function handleInboundPromptId(msg, _context, state) {
|
|
90
106
|
const id = msg.id;
|
|
91
107
|
if (id === null || id === undefined)
|
|
92
108
|
return false;
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
if (state.suppressedLateResponseIds.has(id)) {
|
|
110
|
+
state.suppressedLateResponseIds.delete(id);
|
|
95
111
|
return true;
|
|
96
112
|
}
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
for (const [sessId, cancelling] of state.cancellingSessions.entries()) {
|
|
114
|
+
if (cancelling.promptId === id) {
|
|
115
|
+
clearTimeout(cancelling.timer);
|
|
116
|
+
state.cancellingSessions.delete(sessId);
|
|
117
|
+
cancelling.resolve();
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const [sessId, promptId] of state.activePrompts.entries()) {
|
|
122
|
+
if (promptId === id) {
|
|
123
|
+
state.activePrompts.delete(sessId);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
99
126
|
}
|
|
100
127
|
return false;
|
|
101
128
|
}
|
|
102
|
-
|
|
129
|
+
function isSuppressedDuringCancellation(msg, sessionId, state) {
|
|
130
|
+
if (!sessionId || !state.cancellingSessions.has(sessionId))
|
|
131
|
+
return false;
|
|
132
|
+
if (!isMethod(msg, ACP_METHODS.SESSION_UPDATE))
|
|
133
|
+
return false;
|
|
134
|
+
const update = msg.params?.update;
|
|
135
|
+
return update?.sessionUpdate !== SESSION_UPDATES.TOOL_CALL_UPDATE;
|
|
136
|
+
}
|
|
137
|
+
export function createCancellationLeakFix(options) {
|
|
138
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_CANCELLATION_TIMEOUT_MS;
|
|
103
139
|
const state = {
|
|
104
140
|
activePrompts: new Map(),
|
|
105
|
-
|
|
106
|
-
|
|
141
|
+
cancellingSessions: new Map(),
|
|
142
|
+
suppressedLateResponseIds: new Set(),
|
|
107
143
|
};
|
|
108
144
|
return {
|
|
109
145
|
name: "cancellation-leak",
|
|
110
146
|
description: "Suppresses raw upstream Go/Python cancellation error chunks and ensures prompt settlement during client interruptions",
|
|
111
|
-
onOutbound(msg, context) {
|
|
147
|
+
async onOutbound(msg, context) {
|
|
112
148
|
if (isMethod(msg, ACP_METHODS.SESSION_PROMPT)) {
|
|
113
|
-
handleOutboundPrompt(msg, state);
|
|
149
|
+
await handleOutboundPrompt(msg, state);
|
|
114
150
|
}
|
|
115
151
|
else if (isMethod(msg, ACP_METHODS.SESSION_CANCEL)) {
|
|
116
|
-
handleOutboundCancel(msg, context, state);
|
|
152
|
+
handleOutboundCancel(msg, context, state, timeoutMs);
|
|
117
153
|
}
|
|
118
154
|
return msg;
|
|
119
155
|
},
|
|
@@ -122,10 +158,8 @@ export function createCancellationLeakFix() {
|
|
|
122
158
|
return [];
|
|
123
159
|
}
|
|
124
160
|
const sessionId = extractSessionId(msg) ?? context.session?.sessionId;
|
|
125
|
-
if (sessionId
|
|
126
|
-
|
|
127
|
-
return [];
|
|
128
|
-
}
|
|
161
|
+
if (isSuppressedDuringCancellation(msg, sessionId, state)) {
|
|
162
|
+
return [];
|
|
129
163
|
}
|
|
130
164
|
const text = extractMessageChunkText(msg);
|
|
131
165
|
if (text && isCancellationText(text)) {
|
|
@@ -134,9 +168,13 @@ export function createCancellationLeakFix() {
|
|
|
134
168
|
return [msg];
|
|
135
169
|
},
|
|
136
170
|
dispose() {
|
|
171
|
+
for (const cancelling of state.cancellingSessions.values()) {
|
|
172
|
+
clearTimeout(cancelling.timer);
|
|
173
|
+
cancelling.resolve();
|
|
174
|
+
}
|
|
137
175
|
state.activePrompts.clear();
|
|
138
|
-
state.
|
|
139
|
-
state.
|
|
176
|
+
state.cancellingSessions.clear();
|
|
177
|
+
state.suppressedLateResponseIds.clear();
|
|
140
178
|
},
|
|
141
179
|
};
|
|
142
180
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/fixes/cancellation-leak/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/fixes/cancellation-leak/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,YAAY,EACZ,QAAQ,GAMT,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE/D,MAAM,CAAC,MAAM,wBAAwB,GACnC,qFAAqF,CAAC;AAExF,MAAM,CAAC,MAAM,8BAA8B,GACzC,4IAA4I,CAAC;AAE/I,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAC;AAEpD,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,OAAO,CACL,OAAO,KAAK,kBAAkB;QAC9B,OAAO,KAAK,0CAA0C;QACtD,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC;QACtC,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,CAC7C,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAgB;IAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ;gBAAE,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC;QACpD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3C,CAAC;IACD,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;QAChE,MAAM,IAAI,GAAI,OAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;IAC5C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,GAAqB;IAC3D,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC,cAAc,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,MAAM,MAAM,GAAI,GAAG,CAAC,MAA0C,EAAE,MAAM,CAAC;IACvE,IAAI,MAAM,EAAE,aAAa,KAAK,eAAe,CAAC,mBAAmB,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAClG,OAAO,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC;AAmBD,KAAK,UAAU,oBAAoB,CACjC,GAAqB,EACrB,KAAwB;IAExB,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,EAAE,GAAI,GAAgC,CAAC,EAAE,CAAC;IAChD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,UAAU,GAAG,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3D,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,UAAU,CAAC,OAAO,CAAC;QAC3B,CAAC;QACD,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YACpC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAC3B,GAAqB,EACrB,OAAwB,EACxB,KAAwB,EACxB,SAAiB;IAEjB,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACpD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO;IAEnC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO;IAEpD,IAAI,SAAsB,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAC5C,SAAS,GAAG,OAAO,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACxD,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC7C,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC3C,KAAK,CAAC,yBAAyB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC9C,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;YACtC,CAAC;YACD,MAAM,cAAc,GAAqB;gBACvC,OAAO,EAAE,KAAK;gBACd,EAAE,EAAE,QAAQ;gBACZ,MAAM,EAAE,EAAE,UAAU,EAAE,YAAY,CAAC,SAAS,EAAE;aAC/C,CAAC;YACF,OAAO,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,CAAC;YACzC,SAAS,EAAE,CAAC;QACd,CAAC;IACH,CAAC,EAAE,SAAS,CAAC,CAAC;IACd,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAEhB,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,EAAE;QACtC,QAAQ;QACR,KAAK;QACL,OAAO;QACP,OAAO,EAAE,SAAS;KACnB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,qBAAqB,CAC5B,GAAqB,EACrB,QAAwB,EACxB,KAAwB;IAExB,MAAM,EAAE,GAAI,GAAuC,CAAC,EAAE,CAAC;IACvD,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAElD,IAAI,KAAK,CAAC,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QAC5C,KAAK,CAAC,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,CAAC;QACtE,IAAI,UAAU,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;YAC/B,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC/B,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACxC,UAAU,CAAC,OAAO,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/D,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;YACpB,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACnC,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,8BAA8B,CACrC,GAAqB,EACrB,SAA6B,EAC7B,KAAwB;IAExB,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACzE,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC,cAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,MAAM,MAAM,GAAI,GAAG,CAAC,MAA0C,EAAE,MAAM,CAAC;IACvE,OAAO,MAAM,EAAE,aAAa,KAAK,eAAe,CAAC,gBAAgB,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAA6B;IACrE,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,+BAA+B,CAAC;IACxE,MAAM,KAAK,GAAsB;QAC/B,aAAa,EAAE,IAAI,GAAG,EAAE;QACxB,kBAAkB,EAAE,IAAI,GAAG,EAAE;QAC7B,yBAAyB,EAAE,IAAI,GAAG,EAAE;KACrC,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,WAAW,EACT,uHAAuH;QAEzH,KAAK,CAAC,UAAU,CAAC,GAAqB,EAAE,OAAwB;YAC9D,IAAI,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC,cAAc,CAAC,EAAE,CAAC;gBAC9C,MAAM,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACzC,CAAC;iBAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC,cAAc,CAAC,EAAE,CAAC;gBACrD,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;YACvD,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,SAAS,CAAC,GAAqB,EAAE,OAAuB;YACtD,IAAI,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC/C,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC;YACtE,IAAI,8BAA8B,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC1D,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,MAAM,IAAI,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;QAED,OAAO;YACL,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC3D,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;gBAC/B,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,CAAC;YACD,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;YAC5B,KAAK,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,CAAC,yBAAyB,CAAC,KAAK,EAAE,CAAC;QAC1C,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,yBAAyB,EAAE,CAAC;AAC/D,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC"}
|
|
@@ -17,6 +17,7 @@ export * from "./missing-usage-metrics/index.js";
|
|
|
17
17
|
export * from "./dangling-tool-calls/index.js";
|
|
18
18
|
export * from "./missing-question-fallback/index.js";
|
|
19
19
|
export * from "./premature-turn-stop/index.js";
|
|
20
|
+
export * from "./repetitive-tool-loop/index.js";
|
|
20
21
|
/**
|
|
21
22
|
* Returns a list of all active fixes addressing the upstream issues documented in readme.md.
|
|
22
23
|
* As upstream releases fix these defects, retirement is as simple as removing the fix here
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fixes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/fixes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAqB/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAsB7C"}
|
package/dist/src/fixes/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { missingUsageMetricsFix } from "./missing-usage-metrics/index.js";
|
|
|
16
16
|
import { danglingToolCallsFix } from "./dangling-tool-calls/index.js";
|
|
17
17
|
import { missingQuestionFallbackFix } from "./missing-question-fallback/index.js";
|
|
18
18
|
import { prematureTurnStopFix } from "./premature-turn-stop/index.js";
|
|
19
|
+
import { repetitiveToolLoopFix } from "./repetitive-tool-loop/index.js";
|
|
19
20
|
export * from "./missing-localharness/index.js";
|
|
20
21
|
export * from "./missing-system-prompt/index.js";
|
|
21
22
|
export * from "./missing-slash-skills/index.js";
|
|
@@ -34,6 +35,7 @@ export * from "./missing-usage-metrics/index.js";
|
|
|
34
35
|
export * from "./dangling-tool-calls/index.js";
|
|
35
36
|
export * from "./missing-question-fallback/index.js";
|
|
36
37
|
export * from "./premature-turn-stop/index.js";
|
|
38
|
+
export * from "./repetitive-tool-loop/index.js";
|
|
37
39
|
/**
|
|
38
40
|
* Returns a list of all active fixes addressing the upstream issues documented in readme.md.
|
|
39
41
|
* As upstream releases fix these defects, retirement is as simple as removing the fix here
|
|
@@ -49,6 +51,7 @@ export function createDefaultFixes() {
|
|
|
49
51
|
malformedStreamSyntaxFix,
|
|
50
52
|
activeTurnCollisionFix,
|
|
51
53
|
cancellationLeakFix,
|
|
54
|
+
repetitiveToolLoopFix,
|
|
52
55
|
danglingToolCallsFix,
|
|
53
56
|
staleMcpEndpointsFix,
|
|
54
57
|
orphanedCheckpointsFix,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/fixes/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/fixes/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sCAAsC,CAAC;AACrD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAEhD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,sBAAsB;QACtB,sBAAsB;QACtB,qBAAqB;QACrB,eAAe;QACf,wBAAwB;QACxB,wBAAwB;QACxB,sBAAsB;QACtB,mBAAmB;QACnB,qBAAqB;QACrB,oBAAoB;QACpB,oBAAoB;QACpB,sBAAsB;QACtB,uBAAuB;QACvB,kBAAkB;QAClB,wBAAwB;QACxB,sBAAsB;QACtB,sBAAsB;QACtB,0BAA0B;QAC1B,oBAAoB;KACrB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Problem:
|
|
3
|
+
* Upstream `agy_acp_server` does not detect repetitive tool calling loops or autoregressive attractor cycles.
|
|
4
|
+
* When a model degenerates into repeating the same tool call or cycling through the same set of file slices,
|
|
5
|
+
* raw agy blindly executes tool calls indefinitely, burning thousands of tokens and freezing editor sessions.
|
|
6
|
+
* Furthermore, blindly halting every repeated tool breaks legitimate user polling (e.g. polling a port or waiting for builds).
|
|
7
|
+
*
|
|
8
|
+
* Solution:
|
|
9
|
+
* Tracks canonicalized tool call signatures per prompt turn using suffix cycle matching.
|
|
10
|
+
* Recognizes user polling intent (e.g. "until", "poll", "retry", "wait for", "N times") to allow legitimate polling.
|
|
11
|
+
* When an unintentional runaway loop is detected, interrupts the repetitive execution and sends automated steering
|
|
12
|
+
* ("You have repeatedly executed X, is that expected?") directly to the upstream model so it self-corrects without
|
|
13
|
+
* ending the turn or polluting the user's chat.
|
|
14
|
+
*/
|
|
15
|
+
import { type AcpFix, type AcpStreamMessage } from "../../core/types.js";
|
|
16
|
+
export declare const IGNORED_METADATA_KEYS: Set<string>;
|
|
17
|
+
export declare const READ_ONLY_TOOLS: Set<string>;
|
|
18
|
+
export declare const POLLING_INTENT_PATTERN: RegExp;
|
|
19
|
+
export declare function extractPromptText(msg: AcpStreamMessage): string;
|
|
20
|
+
export declare function hasPollingIntent(promptText: string): boolean;
|
|
21
|
+
export declare function extractRequestedIterations(promptText: string): number | undefined;
|
|
22
|
+
export interface RepetitiveToolLoopOptions {
|
|
23
|
+
maxPeriod?: number;
|
|
24
|
+
readOnlySingleThreshold?: number;
|
|
25
|
+
mutatingSingleThreshold?: number;
|
|
26
|
+
pollingSingleThreshold?: number;
|
|
27
|
+
cycleThreshold?: number;
|
|
28
|
+
maxSteersPerTurn?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function normalizeKey(key: string): string;
|
|
31
|
+
export declare function canonicalizeValue(val: unknown): unknown;
|
|
32
|
+
export declare function inferToolNameFromUpdate(title?: string | null, kind?: string | null, rawInput?: unknown): string;
|
|
33
|
+
export declare function computeToolCallSignature(toolName: string, rawInput: unknown): string;
|
|
34
|
+
export interface CycleDetectionResult {
|
|
35
|
+
isLoop: boolean;
|
|
36
|
+
cycleLength: number;
|
|
37
|
+
repetitions: number;
|
|
38
|
+
pattern: string[];
|
|
39
|
+
}
|
|
40
|
+
export declare function detectCycle(signatures: readonly string[], options?: {
|
|
41
|
+
maxPeriod?: number;
|
|
42
|
+
singleToolThreshold?: number;
|
|
43
|
+
cycleThreshold?: number;
|
|
44
|
+
}): CycleDetectionResult;
|
|
45
|
+
export declare class RepetitiveToolLoopTracker {
|
|
46
|
+
private readonly sessions;
|
|
47
|
+
private readonly promptIdToSessionId;
|
|
48
|
+
private getOrCreate;
|
|
49
|
+
startTurn(sessionId: string, promptId?: string | number, promptText?: string): void;
|
|
50
|
+
getSessionIdForPrompt(promptId: string | number): string | undefined;
|
|
51
|
+
recordToolCall(sessionId: string, toolName: string, rawInput: unknown, options?: RepetitiveToolLoopOptions): CycleDetectionResult;
|
|
52
|
+
isLoopInterrupted(sessionId: string): boolean;
|
|
53
|
+
isSteerPending(sessionId: string): boolean;
|
|
54
|
+
setSteerPending(sessionId: string, pending: boolean): void;
|
|
55
|
+
getSteerPromptId(sessionId: string): string | number | undefined;
|
|
56
|
+
setSteerPromptId(sessionId: string, promptId: string | number): void;
|
|
57
|
+
clearSteerPrompt(sessionId: string): void;
|
|
58
|
+
getSteerCount(sessionId: string): number;
|
|
59
|
+
incrementSteerCount(sessionId: string): void;
|
|
60
|
+
getLastToolName(sessionId: string): string | undefined;
|
|
61
|
+
getLastLoopResult(sessionId: string): CycleDetectionResult | undefined;
|
|
62
|
+
resetSignaturesForSteering(sessionId: string): void;
|
|
63
|
+
getActivePromptId(sessionId: string): string | number | null | undefined;
|
|
64
|
+
hasPollingIntent(sessionId: string): boolean;
|
|
65
|
+
clearSession(sessionId: string): void;
|
|
66
|
+
clear(): void;
|
|
67
|
+
}
|
|
68
|
+
export declare function createRepetitiveToolLoopFix(options?: RepetitiveToolLoopOptions): AcpFix;
|
|
69
|
+
export declare const repetitiveToolLoopFix: AcpFix;
|
|
70
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/fixes/repetitive-tool-loop/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Problem:
|
|
3
|
+
* Upstream `agy_acp_server` does not detect repetitive tool calling loops or autoregressive attractor cycles.
|
|
4
|
+
* When a model degenerates into repeating the same tool call or cycling through the same set of file slices,
|
|
5
|
+
* raw agy blindly executes tool calls indefinitely, burning thousands of tokens and freezing editor sessions.
|
|
6
|
+
* Furthermore, blindly halting every repeated tool breaks legitimate user polling (e.g. polling a port or waiting for builds).
|
|
7
|
+
*
|
|
8
|
+
* Solution:
|
|
9
|
+
* Tracks canonicalized tool call signatures per prompt turn using suffix cycle matching.
|
|
10
|
+
* Recognizes user polling intent (e.g. "until", "poll", "retry", "wait for", "N times") to allow legitimate polling.
|
|
11
|
+
* When an unintentional runaway loop is detected, interrupts the repetitive execution and sends automated steering
|
|
12
|
+
* ("You have repeatedly executed X, is that expected?") directly to the upstream model so it self-corrects without
|
|
13
|
+
* ending the turn or polluting the user's chat.
|
|
14
|
+
*/
|
|
15
|
+
import { ACP_METHODS, SESSION_UPDATES, STOP_REASONS, isMethod, } from "../../core/types.js";
|
|
16
|
+
import { extractSessionId } from "../../core/session-cache.js";
|
|
17
|
+
export const IGNORED_METADATA_KEYS = new Set(["toolaction", "toolsummary", "description"]);
|
|
18
|
+
export const READ_ONLY_TOOLS = new Set([
|
|
19
|
+
"view_file",
|
|
20
|
+
"list_directory",
|
|
21
|
+
"find_file",
|
|
22
|
+
"read_url_content",
|
|
23
|
+
"search_web",
|
|
24
|
+
"read_file",
|
|
25
|
+
]);
|
|
26
|
+
export const POLLING_INTENT_PATTERN = /\b(?:until|poll|polling|wait\s+for|retry|retrying|keep\s+checking|repeat|repeatedly|loop|while|monitor|status\s+code\s+200|\d+\s*times)\b/i;
|
|
27
|
+
export function extractPromptText(msg) {
|
|
28
|
+
const prompt = msg?.params?.prompt;
|
|
29
|
+
if (typeof prompt === "string")
|
|
30
|
+
return prompt;
|
|
31
|
+
if (!Array.isArray(prompt))
|
|
32
|
+
return "";
|
|
33
|
+
return prompt
|
|
34
|
+
.filter((b) => b && typeof b.text === "string")
|
|
35
|
+
.map((b) => b.text)
|
|
36
|
+
.join(" ");
|
|
37
|
+
}
|
|
38
|
+
export function hasPollingIntent(promptText) {
|
|
39
|
+
return POLLING_INTENT_PATTERN.test(promptText);
|
|
40
|
+
}
|
|
41
|
+
export function extractRequestedIterations(promptText) {
|
|
42
|
+
const match = promptText.match(/(\d+)\s*times\b/i);
|
|
43
|
+
if (match && match[1]) {
|
|
44
|
+
const num = Math.trunc(Number(match[1]));
|
|
45
|
+
if (!Number.isNaN(num) && num > 0)
|
|
46
|
+
return num;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
export function normalizeKey(key) {
|
|
51
|
+
return key.toLowerCase().replace(/_/g, "");
|
|
52
|
+
}
|
|
53
|
+
export function canonicalizeValue(val) {
|
|
54
|
+
if (val === null || val === undefined)
|
|
55
|
+
return null;
|
|
56
|
+
if (typeof val !== "object")
|
|
57
|
+
return val;
|
|
58
|
+
if (Array.isArray(val)) {
|
|
59
|
+
return val.map(canonicalizeValue);
|
|
60
|
+
}
|
|
61
|
+
const sortedObj = {};
|
|
62
|
+
const entries = Object.entries(val);
|
|
63
|
+
const filteredEntries = entries.filter(([k]) => !IGNORED_METADATA_KEYS.has(normalizeKey(k)));
|
|
64
|
+
filteredEntries.sort(([a], [b]) => normalizeKey(a).localeCompare(normalizeKey(b)));
|
|
65
|
+
for (const [k, v] of filteredEntries) {
|
|
66
|
+
sortedObj[normalizeKey(k)] = canonicalizeValue(v);
|
|
67
|
+
}
|
|
68
|
+
return sortedObj;
|
|
69
|
+
}
|
|
70
|
+
export function inferToolNameFromUpdate(title, kind, rawInput) {
|
|
71
|
+
if (title) {
|
|
72
|
+
const cleaned = title.replace(/^Running:?\s+/i, "").trim();
|
|
73
|
+
if (cleaned.length > 0)
|
|
74
|
+
return cleaned;
|
|
75
|
+
}
|
|
76
|
+
if (kind && kind !== "other") {
|
|
77
|
+
return kind;
|
|
78
|
+
}
|
|
79
|
+
if (rawInput && typeof rawInput === "object") {
|
|
80
|
+
if ("CommandLine" in rawInput)
|
|
81
|
+
return "run_command";
|
|
82
|
+
if ("AbsolutePath" in rawInput)
|
|
83
|
+
return "view_file";
|
|
84
|
+
if ("Subagents" in rawInput)
|
|
85
|
+
return "invoke_subagent";
|
|
86
|
+
}
|
|
87
|
+
return "tool";
|
|
88
|
+
}
|
|
89
|
+
export function computeToolCallSignature(toolName, rawInput) {
|
|
90
|
+
let parsedInput = rawInput;
|
|
91
|
+
if (typeof rawInput === "string") {
|
|
92
|
+
try {
|
|
93
|
+
parsedInput = JSON.parse(rawInput);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
parsedInput = rawInput;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const canonical = canonicalizeValue(parsedInput);
|
|
100
|
+
return `${toolName}:${JSON.stringify(canonical)}`;
|
|
101
|
+
}
|
|
102
|
+
function isSuffixCycleMatch(signatures, n, k, repetitions) {
|
|
103
|
+
for (let r = 1; r < repetitions; r++) {
|
|
104
|
+
for (let i = 0; i < k; i++) {
|
|
105
|
+
if (signatures[n - k + i] !== signatures[n - (r + 1) * k + i]) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
function isDegenerateSingleItem(signatures, n, k) {
|
|
113
|
+
if (k <= 1)
|
|
114
|
+
return false;
|
|
115
|
+
for (let i = 1; i < k; i++) {
|
|
116
|
+
if (signatures[n - k + i] !== signatures[n - k]) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
export function detectCycle(signatures, options = {}) {
|
|
123
|
+
const maxPeriod = options.maxPeriod ?? 4;
|
|
124
|
+
const singleToolThreshold = options.singleToolThreshold ?? 3;
|
|
125
|
+
const cycleThreshold = options.cycleThreshold ?? 3;
|
|
126
|
+
const n = signatures.length;
|
|
127
|
+
for (let k = 1; k <= maxPeriod; k++) {
|
|
128
|
+
const requiredRepetitions = k === 1 ? singleToolThreshold : cycleThreshold;
|
|
129
|
+
const requiredLength = k * requiredRepetitions;
|
|
130
|
+
if (n < requiredLength || isDegenerateSingleItem(signatures, n, k)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (isSuffixCycleMatch(signatures, n, k, requiredRepetitions)) {
|
|
134
|
+
return {
|
|
135
|
+
isLoop: true,
|
|
136
|
+
cycleLength: k,
|
|
137
|
+
repetitions: requiredRepetitions,
|
|
138
|
+
pattern: signatures.slice(n - k),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
isLoop: false,
|
|
144
|
+
cycleLength: 0,
|
|
145
|
+
repetitions: 0,
|
|
146
|
+
pattern: [],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function resolveSingleThreshold(isReadOnly, hasPolling, requestedIterations, options) {
|
|
150
|
+
if (isReadOnly)
|
|
151
|
+
return options.readOnlySingleThreshold ?? 3;
|
|
152
|
+
if (!hasPolling)
|
|
153
|
+
return options.mutatingSingleThreshold ?? 5;
|
|
154
|
+
const basePolling = options.pollingSingleThreshold ?? 30;
|
|
155
|
+
return requestedIterations ? Math.max(requestedIterations + 2, basePolling) : basePolling;
|
|
156
|
+
}
|
|
157
|
+
export class RepetitiveToolLoopTracker {
|
|
158
|
+
sessions = new Map();
|
|
159
|
+
promptIdToSessionId = new Map();
|
|
160
|
+
getOrCreate(sessionId) {
|
|
161
|
+
let state = this.sessions.get(sessionId);
|
|
162
|
+
if (!state) {
|
|
163
|
+
state = {
|
|
164
|
+
signatures: [],
|
|
165
|
+
loopInterrupted: false,
|
|
166
|
+
userPromptText: "",
|
|
167
|
+
hasPollingIntent: false,
|
|
168
|
+
steerPending: false,
|
|
169
|
+
steerCount: 0,
|
|
170
|
+
};
|
|
171
|
+
this.sessions.set(sessionId, state);
|
|
172
|
+
}
|
|
173
|
+
return state;
|
|
174
|
+
}
|
|
175
|
+
startTurn(sessionId, promptId, promptText = "") {
|
|
176
|
+
const state = this.getOrCreate(sessionId);
|
|
177
|
+
state.activePromptId = promptId;
|
|
178
|
+
state.userPromptText = promptText;
|
|
179
|
+
state.hasPollingIntent = hasPollingIntent(promptText);
|
|
180
|
+
state.requestedIterations = extractRequestedIterations(promptText);
|
|
181
|
+
state.signatures = [];
|
|
182
|
+
state.loopInterrupted = false;
|
|
183
|
+
state.steerPending = false;
|
|
184
|
+
state.steerPromptId = undefined;
|
|
185
|
+
state.steerCount = 0;
|
|
186
|
+
state.lastLoopResult = undefined;
|
|
187
|
+
state.lastToolName = undefined;
|
|
188
|
+
if (promptId !== undefined && promptId !== null) {
|
|
189
|
+
this.promptIdToSessionId.set(promptId, sessionId);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
getSessionIdForPrompt(promptId) {
|
|
193
|
+
return this.promptIdToSessionId.get(promptId);
|
|
194
|
+
}
|
|
195
|
+
recordToolCall(sessionId, toolName, rawInput, options = {}) {
|
|
196
|
+
const state = this.getOrCreate(sessionId);
|
|
197
|
+
if (state.loopInterrupted) {
|
|
198
|
+
return { isLoop: true, cycleLength: 0, repetitions: 0, pattern: [] };
|
|
199
|
+
}
|
|
200
|
+
const signature = computeToolCallSignature(toolName, rawInput);
|
|
201
|
+
state.signatures.push(signature);
|
|
202
|
+
const isReadOnly = READ_ONLY_TOOLS.has(toolName);
|
|
203
|
+
const singleThreshold = resolveSingleThreshold(isReadOnly, state.hasPollingIntent, state.requestedIterations, options);
|
|
204
|
+
const result = detectCycle(state.signatures, {
|
|
205
|
+
maxPeriod: options.maxPeriod ?? 4,
|
|
206
|
+
singleToolThreshold: singleThreshold,
|
|
207
|
+
cycleThreshold: options.cycleThreshold ?? 3,
|
|
208
|
+
});
|
|
209
|
+
if (result.isLoop) {
|
|
210
|
+
state.loopInterrupted = true;
|
|
211
|
+
state.lastLoopResult = result;
|
|
212
|
+
state.lastToolName = toolName;
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
isLoopInterrupted(sessionId) {
|
|
217
|
+
return this.sessions.get(sessionId)?.loopInterrupted ?? false;
|
|
218
|
+
}
|
|
219
|
+
isSteerPending(sessionId) {
|
|
220
|
+
return this.sessions.get(sessionId)?.steerPending ?? false;
|
|
221
|
+
}
|
|
222
|
+
setSteerPending(sessionId, pending) {
|
|
223
|
+
const s = this.sessions.get(sessionId);
|
|
224
|
+
if (s)
|
|
225
|
+
s.steerPending = pending;
|
|
226
|
+
}
|
|
227
|
+
getSteerPromptId(sessionId) {
|
|
228
|
+
return this.sessions.get(sessionId)?.steerPromptId;
|
|
229
|
+
}
|
|
230
|
+
setSteerPromptId(sessionId, promptId) {
|
|
231
|
+
const s = this.sessions.get(sessionId);
|
|
232
|
+
if (s) {
|
|
233
|
+
s.steerPromptId = promptId;
|
|
234
|
+
this.promptIdToSessionId.set(promptId, sessionId);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
clearSteerPrompt(sessionId) {
|
|
238
|
+
const s = this.sessions.get(sessionId);
|
|
239
|
+
if (s) {
|
|
240
|
+
if (s.steerPromptId !== undefined) {
|
|
241
|
+
this.promptIdToSessionId.delete(s.steerPromptId);
|
|
242
|
+
}
|
|
243
|
+
s.steerPromptId = undefined;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
getSteerCount(sessionId) {
|
|
247
|
+
return this.sessions.get(sessionId)?.steerCount ?? 0;
|
|
248
|
+
}
|
|
249
|
+
incrementSteerCount(sessionId) {
|
|
250
|
+
const s = this.sessions.get(sessionId);
|
|
251
|
+
if (s)
|
|
252
|
+
s.steerCount++;
|
|
253
|
+
}
|
|
254
|
+
getLastToolName(sessionId) {
|
|
255
|
+
return this.sessions.get(sessionId)?.lastToolName;
|
|
256
|
+
}
|
|
257
|
+
getLastLoopResult(sessionId) {
|
|
258
|
+
return this.sessions.get(sessionId)?.lastLoopResult;
|
|
259
|
+
}
|
|
260
|
+
resetSignaturesForSteering(sessionId) {
|
|
261
|
+
const s = this.sessions.get(sessionId);
|
|
262
|
+
if (s) {
|
|
263
|
+
s.signatures = [];
|
|
264
|
+
s.loopInterrupted = false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
getActivePromptId(sessionId) {
|
|
268
|
+
return this.sessions.get(sessionId)?.activePromptId;
|
|
269
|
+
}
|
|
270
|
+
hasPollingIntent(sessionId) {
|
|
271
|
+
return this.sessions.get(sessionId)?.hasPollingIntent ?? false;
|
|
272
|
+
}
|
|
273
|
+
clearSession(sessionId) {
|
|
274
|
+
const s = this.sessions.get(sessionId);
|
|
275
|
+
if (s) {
|
|
276
|
+
if (s.activePromptId !== undefined && s.activePromptId !== null) {
|
|
277
|
+
this.promptIdToSessionId.delete(s.activePromptId);
|
|
278
|
+
}
|
|
279
|
+
if (s.steerPromptId !== undefined) {
|
|
280
|
+
this.promptIdToSessionId.delete(s.steerPromptId);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
this.sessions.delete(sessionId);
|
|
284
|
+
}
|
|
285
|
+
clear() {
|
|
286
|
+
this.sessions.clear();
|
|
287
|
+
this.promptIdToSessionId.clear();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function interruptLoopExecution(sessionId, toolCallId, _toolName, _loopResult, tracker, context) {
|
|
291
|
+
// Cancel upstream execution so the model stops repeating the tool
|
|
292
|
+
await context
|
|
293
|
+
.writeToChild({
|
|
294
|
+
jsonrpc: "2.0",
|
|
295
|
+
method: ACP_METHODS.SESSION_CANCEL,
|
|
296
|
+
params: { sessionId },
|
|
297
|
+
})
|
|
298
|
+
.catch(() => { });
|
|
299
|
+
// Mark the pending tool call as completed in the client UI so it doesn't spin
|
|
300
|
+
context.forwardInbound?.({
|
|
301
|
+
jsonrpc: "2.0",
|
|
302
|
+
method: ACP_METHODS.SESSION_UPDATE,
|
|
303
|
+
params: {
|
|
304
|
+
sessionId,
|
|
305
|
+
update: {
|
|
306
|
+
sessionUpdate: SESSION_UPDATES.TOOL_CALL_UPDATE,
|
|
307
|
+
toolCallId,
|
|
308
|
+
status: "completed",
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
// Mark that this session should receive automated steering when upstream finishes cancellation
|
|
313
|
+
tracker.setSteerPending(sessionId, true);
|
|
314
|
+
}
|
|
315
|
+
function handleSteerPromptCompletion(msg, sessionId, tracker) {
|
|
316
|
+
const promptId = msg.id;
|
|
317
|
+
const steerPromptId = tracker.getSteerPromptId(sessionId);
|
|
318
|
+
if (steerPromptId !== undefined && promptId === steerPromptId) {
|
|
319
|
+
const originalPromptId = tracker.getActivePromptId(sessionId);
|
|
320
|
+
tracker.clearSteerPrompt(sessionId);
|
|
321
|
+
return [{ ...msg, id: originalPromptId ?? null }];
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
async function dispatchSteeringPrompt(sessionId, tracker, context) {
|
|
326
|
+
tracker.setSteerPending(sessionId, false);
|
|
327
|
+
tracker.incrementSteerCount(sessionId);
|
|
328
|
+
tracker.resetSignaturesForSteering(sessionId);
|
|
329
|
+
const toolName = tracker.getLastToolName(sessionId) ?? "tool";
|
|
330
|
+
const loopResult = tracker.getLastLoopResult(sessionId);
|
|
331
|
+
const repetitions = loopResult?.repetitions ?? 3;
|
|
332
|
+
const steeringText = `[Automated Steering]: You have repeatedly executed '${toolName}' (${repetitions} times consecutively with identical parameters) without progress. ` +
|
|
333
|
+
`Is this expected? If you are polling or waiting on an external state change, explain what you are waiting for. Otherwise, please stop repeating this action, evaluate your findings, and try a different approach or report your status to the user.`;
|
|
334
|
+
const newSteerPromptId = `steer_${sessionId}_${Date.now()}`;
|
|
335
|
+
tracker.setSteerPromptId(sessionId, newSteerPromptId);
|
|
336
|
+
try {
|
|
337
|
+
await context.writeToChild({
|
|
338
|
+
jsonrpc: "2.0",
|
|
339
|
+
id: newSteerPromptId,
|
|
340
|
+
method: ACP_METHODS.SESSION_PROMPT,
|
|
341
|
+
params: {
|
|
342
|
+
sessionId,
|
|
343
|
+
prompt: [{ type: "text", text: steeringText }],
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
console.error(`[refined-antigravity-acp] Failed to send automated steering prompt:`, err);
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function extractCancelledResult(msg, sessionId, tracker) {
|
|
354
|
+
if (!tracker.isLoopInterrupted(sessionId) || !("result" in msg))
|
|
355
|
+
return null;
|
|
356
|
+
const res = msg.result;
|
|
357
|
+
if (!res)
|
|
358
|
+
return null;
|
|
359
|
+
if (res.stopReason && res.stopReason !== STOP_REASONS.CANCELLED)
|
|
360
|
+
return null;
|
|
361
|
+
return res;
|
|
362
|
+
}
|
|
363
|
+
async function settleInterruptedPromptResponse(msg, sessionId, tracker, context, options) {
|
|
364
|
+
if (!("result" in msg))
|
|
365
|
+
return null;
|
|
366
|
+
const steerResult = handleSteerPromptCompletion(msg, sessionId, tracker);
|
|
367
|
+
if (steerResult)
|
|
368
|
+
return steerResult;
|
|
369
|
+
const res = extractCancelledResult(msg, sessionId, tracker);
|
|
370
|
+
if (!res)
|
|
371
|
+
return null;
|
|
372
|
+
const maxSteers = options.maxSteersPerTurn ?? 1;
|
|
373
|
+
const canSteer = tracker.isSteerPending(sessionId) && tracker.getSteerCount(sessionId) < maxSteers;
|
|
374
|
+
if (canSteer && (await dispatchSteeringPrompt(sessionId, tracker, context))) {
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
tracker.startTurn(sessionId);
|
|
378
|
+
return [{ ...msg, result: { ...res, stopReason: STOP_REASONS.END_TURN } }];
|
|
379
|
+
}
|
|
380
|
+
function resolveSessionId(msg, tracker) {
|
|
381
|
+
const fromMsg = extractSessionId(msg);
|
|
382
|
+
if (fromMsg)
|
|
383
|
+
return fromMsg;
|
|
384
|
+
const id = msg.id;
|
|
385
|
+
return id !== null && id !== undefined ? tracker.getSessionIdForPrompt(id) : undefined;
|
|
386
|
+
}
|
|
387
|
+
async function handleInboundSessionUpdate(msg, sessionId, tracker, options, context) {
|
|
388
|
+
const update = "params" in msg ? msg.params?.update : undefined;
|
|
389
|
+
if (update?.sessionUpdate !== SESSION_UPDATES.TOOL_CALL) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const toolCallId = update.toolCallId ?? "unknown";
|
|
393
|
+
const toolName = inferToolNameFromUpdate(update.title, update.kind, update.rawInput);
|
|
394
|
+
const loopResult = tracker.recordToolCall(sessionId, toolName, update.rawInput, options);
|
|
395
|
+
if (loopResult.isLoop) {
|
|
396
|
+
await interruptLoopExecution(sessionId, toolCallId, toolName, loopResult, tracker, context);
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
export function createRepetitiveToolLoopFix(options = {}) {
|
|
402
|
+
const tracker = new RepetitiveToolLoopTracker();
|
|
403
|
+
return {
|
|
404
|
+
name: "repetitive-tool-loop",
|
|
405
|
+
description: "Detects and steers repetitive tool calling cycles and autoregressive attractor loops without breaking polling",
|
|
406
|
+
onOutbound(msg, _context) {
|
|
407
|
+
const sessionId = extractSessionId(msg);
|
|
408
|
+
if (!sessionId)
|
|
409
|
+
return msg;
|
|
410
|
+
if (isMethod(msg, ACP_METHODS.SESSION_PROMPT)) {
|
|
411
|
+
const promptId = msg.id;
|
|
412
|
+
const promptText = extractPromptText(msg);
|
|
413
|
+
tracker.startTurn(sessionId, promptId, promptText);
|
|
414
|
+
}
|
|
415
|
+
else if (isMethod(msg, ACP_METHODS.SESSION_CANCEL)) {
|
|
416
|
+
tracker.clearSteerPrompt(sessionId);
|
|
417
|
+
}
|
|
418
|
+
return msg;
|
|
419
|
+
},
|
|
420
|
+
async onInbound(msg, context) {
|
|
421
|
+
const sessionId = resolveSessionId(msg, tracker);
|
|
422
|
+
if (!sessionId)
|
|
423
|
+
return [msg];
|
|
424
|
+
const settled = await settleInterruptedPromptResponse(msg, sessionId, tracker, context, options);
|
|
425
|
+
if (settled)
|
|
426
|
+
return settled;
|
|
427
|
+
if (isMethod(msg, ACP_METHODS.SESSION_UPDATE)) {
|
|
428
|
+
const handled = await handleInboundSessionUpdate(msg, sessionId, tracker, options, context);
|
|
429
|
+
if (handled)
|
|
430
|
+
return handled;
|
|
431
|
+
}
|
|
432
|
+
return [msg];
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
export const repetitiveToolLoopFix = createRepetitiveToolLoopFix();
|
|
437
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/fixes/repetitive-tool-loop/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -160,6 +160,7 @@ The matrix below documents upstream defects across process startup, turn executi
|
|
|
160
160
|
| [**Dangling Running Tool Calls**](src/fixes/dangling-tool-calls/index.ts) | **Problem**: When commands are backgrounded, upstream never emits terminal `tool_call_update` with `status: "completed"`, leaving editor UIs with an infinite active running animation.<br>🔗 [`problem: dangling running tool call`](src/fixes/dangling-tool-calls/index.test.ts#L67) | Tracks active tool calls and marks backgrounded tools completed immediately, before assistant text streams, and upon turn completion.<br>🔗 [`solution: tool call auto-completion`](src/fixes/dangling-tool-calls/index.test.ts#L79) |
|
|
161
161
|
| [**Interactive Question Deadlock**](src/fixes/missing-question-fallback/index.ts) | **Problem**: When `ask_question` is active, sending a chat prompt or cancel causes upstream to reject the message with _"A foreground turn is already active"_, deadlocking sessions indefinitely.<br>🔗 [`problem: question collision`](src/fixes/missing-question-fallback/index.e2e.test.ts#L16) | Intercepts user chat prompts and cancellations to auto-unblock pending question permission requests to the child process and complete UI tool states.<br>🔗 [`solution: question unblocking`](src/fixes/missing-question-fallback/index.e2e.test.ts#L53) |
|
|
162
162
|
| [**Premature Empty Turn Stop**](src/fixes/premature-turn-stop/index.ts) | **Problem**: Upstream binary prematurely concludes prompt turns with empty completions (`stopReason=16`) without emitting assistant message chunks, leaving editor UIs frozen in silence.<br>🔗 [`problem: premature turn termination`](src/fixes/premature-turn-stop/index.test.ts#L48) | Injects explicit system prompt steering instructing the model to always emit clear status messages and never terminate turns silently.<br>🔗 [`solution: prompt steering`](src/fixes/premature-turn-stop/index.test.ts#L79) |
|
|
163
|
+
| [**Repetitive Tool Calling Loop**](src/fixes/repetitive-tool-loop/index.ts) | **Problem**: Upstream binary has no loop detector and will execute cyclic or identical tool calls indefinitely, burning thousands of tokens and freezing sessions.<br>🔗 [`problem: repetitive tool loop`](src/fixes/repetitive-tool-loop/index.e2e.test.ts#L25) | Tracks tool signatures per turn using suffix cycle matching, sending `session/cancel` upstream, auto-completing tools, and notifying the user when cycles repeat.<br>🔗 [`solution: loop circuit breaker`](src/fixes/repetitive-tool-loop/index.e2e.test.ts#L81) |
|
|
163
164
|
|
|
164
165
|
## Setup
|
|
165
166
|
|