jonah-fleet 1.20.0 → 1.21.0
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/CHANGELOG.md +15 -13
- package/dist/index.js +40 -32
- package/dist/lib/loop-guard.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/prompts/ORCHESTRATION.md +3 -3
- package/templates/prompts/autowork.md +2 -2
- package/templates/prompts/issues-housekeeping.md +4 -2
- package/templates/scripts/run-with-loop-guard.js +55 -35
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to `jonah-fleet` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.21.0](https://github.com/juliendurandeu/jonah-fleet/compare/v1.20.0...v1.21.0) (2026-09-20)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **guard:** exempt manage_task status polling from loop guard and expedite routine failure pruning ([#305](https://github.com/juliendurandeu/jonah-fleet/issues/305)) ([d6e5670](https://github.com/juliendurandeu/jonah-fleet/commit/d6e567029016d4bebaaab8d517ce1daea60b5a3c))
|
|
14
|
+
|
|
15
|
+
## [Unreleased]
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **guard:** exempt `manage_task` status and list polling from action repetition and ping-pong circuit breaker trips in `run-with-loop-guard.js`.
|
|
20
|
+
* **housekeeping:** close `status:failure` routine log issues immediately (0-hour delay) if target is already closed or merged, and lower untargeted runner crash threshold to 12 hours.
|
|
21
|
+
* **autowork:** update Step 9 idle sweep to close resolved-target failures immediately and untargeted crashes after 12 hours.
|
|
22
|
+
|
|
8
23
|
## [1.20.0](https://github.com/juliendurandeu/jonah-fleet/compare/v1.19.0...v1.20.0) (2026-09-20)
|
|
9
24
|
|
|
10
25
|
|
|
@@ -33,19 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
33
48
|
|
|
34
49
|
* **orchestration:** adopt 4-part human escalation card in peer-review routine ([#235](https://github.com/juliendurandeu/jonah-fleet/issues/235)) ([#256](https://github.com/juliendurandeu/jonah-fleet/issues/256)) ([053ef9b](https://github.com/juliendurandeu/jonah-fleet/commit/053ef9b48b7d19b1a503f59c9755b74b99624cc8))
|
|
35
50
|
|
|
36
|
-
## [Unreleased]
|
|
37
|
-
|
|
38
|
-
### Features
|
|
39
|
-
|
|
40
|
-
* **guard:** execute routine GitHub Actions workflows via run-with-loop-guard wrapper script ([#227](https://github.com/juliendurandeu/jonah-fleet/issues/227))
|
|
41
|
-
* **orchestration:** adopt 4-part human escalation card in peer-review routine on round-5 escalation ([#235](https://github.com/juliendurandeu/jonah-fleet/issues/235))
|
|
42
|
-
* **orchestration:** add Routine Run Failure Ingestion & Auto-Closure Protocol to `ORCHESTRATION.md`, unifying prior failure memory and resolution lifecycle across `autowork` and `peer-review`.
|
|
43
|
-
* **prompts:** add Dynamic Target Binding in Scan mode to `autowork.md` and `peer-review.md`, dynamically updating `$ROUTINE_ISSUE_NUMBER` titles with `(Issue #N)` or `(PR #M)` upon claiming so crashed runs are never anonymous.
|
|
44
|
-
* **prompts:** add Prior Failure Ingestion Protocol to `autowork.md` and `peer-review.md`, inspecting past open failed routine issues to extract error logs and crash milestones before implementation or review.
|
|
45
|
-
* **prompts:** add resolution-triggered auto-closure to `autowork.md` and `peer-review.md`, automatically commenting on and closing past failed routine issues for a target once work is marked ready or merged.
|
|
46
|
-
* **housekeeping:** expand `issues-housekeeping.md` to sweep and close stale untargeted `status:failure` routine issues older than 48 hours or whose targets are resolved, eliminating backlog clutter.
|
|
47
|
-
* **autowork:** add opportunistic operational log sweep (idle GC) to Step 9 in `autowork.md`, cleaning up stale untargeted failures when the issue backlog is clear.
|
|
48
|
-
|
|
49
51
|
## [1.16.0](https://github.com/juliendurandeu/jonah-fleet/compare/v1.15.1...v1.16.0) (2026-09-19)
|
|
50
52
|
|
|
51
53
|
|
package/dist/index.js
CHANGED
|
@@ -3585,6 +3585,7 @@ var LoopGuard = class {
|
|
|
3585
3585
|
if (this.trippedResult) {
|
|
3586
3586
|
return this.trippedResult;
|
|
3587
3587
|
}
|
|
3588
|
+
const isStatusPolling = toolName === "manage_task" && (args?.Action === "status" || args?.action === "status" || args?.Action === "list" || args?.action === "list");
|
|
3588
3589
|
const hash = computeActionHash(toolName, args);
|
|
3589
3590
|
const record = {
|
|
3590
3591
|
toolName,
|
|
@@ -3616,43 +3617,50 @@ var LoopGuard = class {
|
|
|
3616
3617
|
this.history.push(record);
|
|
3617
3618
|
const windowStart = Math.max(0, this.history.length - this.slidingWindowSize);
|
|
3618
3619
|
const currentWindow = this.history.slice(windowStart);
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3620
|
+
if (!isStatusPolling) {
|
|
3621
|
+
let repetitionCount = 0;
|
|
3622
|
+
for (const item of currentWindow) {
|
|
3623
|
+
if (item.hash === hash) {
|
|
3624
|
+
repetitionCount++;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
if (repetitionCount >= this.repetitionThreshold) {
|
|
3628
|
+
return this.triggerTrip({
|
|
3629
|
+
reason: "repetition",
|
|
3630
|
+
message: `Action repetition loop detected: tool '${toolName}' called ${repetitionCount} times with identical parameters within sliding window of ${this.slidingWindowSize}.`,
|
|
3631
|
+
toolName,
|
|
3632
|
+
actionHash: hash,
|
|
3633
|
+
count: repetitionCount
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3633
3636
|
}
|
|
3634
3637
|
const requiredPingPongLength = this.pingPongThreshold * 2;
|
|
3635
3638
|
if (this.history.length >= requiredPingPongLength) {
|
|
3636
3639
|
const pingPongSlice = this.history.slice(-requiredPingPongLength);
|
|
3637
|
-
const
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3640
|
+
const hasStatusPolling = pingPongSlice.some(
|
|
3641
|
+
(item) => item.toolName === "manage_task" && (item.args?.Action === "status" || item.args?.action === "status" || item.args?.Action === "list" || item.args?.action === "list")
|
|
3642
|
+
);
|
|
3643
|
+
if (!hasStatusPolling) {
|
|
3644
|
+
const hashA = pingPongSlice[0].hash;
|
|
3645
|
+
const hashB = pingPongSlice[1].hash;
|
|
3646
|
+
if (hashA !== hashB) {
|
|
3647
|
+
let isPingPong = true;
|
|
3648
|
+
for (let i = 0; i < requiredPingPongLength; i++) {
|
|
3649
|
+
const expectedHash = i % 2 === 0 ? hashA : hashB;
|
|
3650
|
+
if (pingPongSlice[i].hash !== expectedHash) {
|
|
3651
|
+
isPingPong = false;
|
|
3652
|
+
break;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
if (isPingPong) {
|
|
3656
|
+
return this.triggerTrip({
|
|
3657
|
+
reason: "ping_pong",
|
|
3658
|
+
message: `Alternating ping-pong action loop detected: ${this.pingPongThreshold} consecutive alternating cycles between tools (A-B-A-B-A-B).`,
|
|
3659
|
+
toolName,
|
|
3660
|
+
actionHash: hash,
|
|
3661
|
+
count: this.pingPongThreshold
|
|
3662
|
+
});
|
|
3646
3663
|
}
|
|
3647
|
-
}
|
|
3648
|
-
if (isPingPong) {
|
|
3649
|
-
return this.triggerTrip({
|
|
3650
|
-
reason: "ping_pong",
|
|
3651
|
-
message: `Alternating ping-pong action loop detected: ${this.pingPongThreshold} consecutive alternating cycles between tools (A-B-A-B-A-B).`,
|
|
3652
|
-
toolName,
|
|
3653
|
-
actionHash: hash,
|
|
3654
|
-
count: this.pingPongThreshold
|
|
3655
|
-
});
|
|
3656
3664
|
}
|
|
3657
3665
|
}
|
|
3658
3666
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-guard.d.ts","sourceRoot":"","sources":["../../src/lib/loop-guard.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,WAAW,GAAG,oBAAoB,CAAC;AAEpF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,GAAG,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAarD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,MAAM,CAGrE;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,MAAM,CAAC,CAAgC;IAE/C,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,aAAa,CAA8B;IAEnD,YAAY,OAAO,GAAE,gBAAqB,EAMzC;IAEM,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEM,SAAS,IAAI,OAAO,CAE1B;IAEM,OAAO,IAAI,aAAa,GAAG,IAAI,CAErC;IAEM,UAAU,IAAI,YAAY,EAAE,CAElC;IAEM,KAAK,IAAI,IAAI,CAKnB;IAED;;OAEG;IACI,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,GAAE,OAAe,GAAG,aAAa,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"loop-guard.d.ts","sourceRoot":"","sources":["../../src/lib/loop-guard.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,WAAW,GAAG,oBAAoB,CAAC;AAEpF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,GAAG,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAarD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,MAAM,CAGrE;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,MAAM,CAAC,CAAgC;IAE/C,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,aAAa,CAA8B;IAEnD,YAAY,OAAO,GAAE,gBAAqB,EAMzC;IAEM,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEM,SAAS,IAAI,OAAO,CAE1B;IAEM,OAAO,IAAI,aAAa,GAAG,IAAI,CAErC;IAEM,UAAU,IAAI,YAAY,EAAE,CAElC;IAEM,KAAK,IAAI,IAAI,CAKnB;IAED;;OAEG;IACI,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,GAAE,OAAe,GAAG,aAAa,GAAG,IAAI,CAiH/F;IAED;;OAEG;IACI,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAwBlD;IAED,OAAO,CAAC,WAAW;CAWpB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM,CAqBvF;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,aAAa,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CA0B7E"}
|
package/package.json
CHANGED
|
@@ -168,9 +168,9 @@ Single source of truth for handling routine run failures, context extraction, an
|
|
|
168
168
|
```
|
|
169
169
|
(or `PR #<PR_NUMBER>`). If prior failed runs exist, the runner extracts error messages, failing test names, or crash milestones from the Interruption Card and incorporates this context into Milestone 1 and its reproduction strategy, preventing repeated identical failures.
|
|
170
170
|
3. **Resolution-Triggered Auto-Closure**: When a target issue or PR reaches terminal completion (a ready PR opened, review decision executed, or PR merged), the routine iterates through any open past failed routine issues for that target, posts a resolution comment referencing the successful run (including the Antigravity run footer), and closes them via `gh issue close <ISSUE> --reason completed`.
|
|
171
|
-
4. **Garbage Collection for Untargeted / Stale Crashes (Housekeeping & Idle Autowork)**: Routine runs
|
|
172
|
-
-
|
|
173
|
-
-
|
|
171
|
+
4. **Garbage Collection for Untargeted / Stale Crashes (Housekeeping & Idle Autowork)**: Routine failure runs are audited and closed by `issues-housekeeping.md` (scheduled sweeps) and `autowork.md` Step 9 (opportunistic idle sweep):
|
|
172
|
+
- **Target Resolved**: If the routine failure references a target issue or PR that is already closed or merged, it is closed **immediately** (0-hour delay).
|
|
173
|
+
- **Untargeted Runner Crash**: Routine runs that fail before claiming a target (e.g. runner VM startup errors, GitHub CLI auth failures, or generic crashes without a target) are closed once older than **12 hours**.
|
|
174
174
|
|
|
175
175
|
---
|
|
176
176
|
|
|
@@ -172,10 +172,10 @@ _Generated by [Antigravity](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/ru
|
|
|
172
172
|
STALE_FAILURES=$(gh issue list --label routine-log,status:failure --state open --limit 20 --json number,title,body,createdAt || true)
|
|
173
173
|
```
|
|
174
174
|
For each stale failure issue:
|
|
175
|
-
- Check if it is untargeted (no `(Issue #N)` or `(PR #M)` in title/body) and older than
|
|
175
|
+
- Check if it is untargeted (no `(Issue #N)` or `(PR #M)` in title/body) and older than 12 hours, OR if its referenced target issue or PR is already closed or merged.
|
|
176
176
|
- If eligible, close it with a resolution note:
|
|
177
177
|
```bash
|
|
178
|
-
gh issue comment "$stale_num" --body "Closed by autowork idle sweep: routine failure superseded or untargeted crash
|
|
178
|
+
gh issue comment "$stale_num" --body "Closed by autowork idle sweep: routine failure superseded or untargeted crash.\n\n_Generated by [Antigravity](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID:-})_" || true
|
|
179
179
|
gh issue close "$stale_num" --reason completed || true
|
|
180
180
|
```
|
|
181
181
|
- STOP — run is SUCCESS with "No unclaimed work available; swept stale operational logs".
|
|
@@ -23,7 +23,7 @@ The run is SUCCESS only if ALL of these are true:
|
|
|
23
23
|
- [ ] Label audit completed: every open issue has consistent type, size, and priority labels
|
|
24
24
|
- [ ] Dependency check completed: issues with `## Dependencies` verified against blocker status
|
|
25
25
|
- [ ] Orphaned-claim sweep completed: stale autowork claims (per `ORCHESTRATION.md`) released back to the unclaimed pool
|
|
26
|
-
- [ ] Stalled routine run audit and stale failure sweep completed: routine log issues in `status:running` older than 6 hours marked as `status:failure`;
|
|
26
|
+
- [ ] Stalled routine run audit and stale failure sweep completed: routine log issues in `status:running` older than 6 hours marked as `status:failure`; `status:failure` routine log issues referencing an already closed/merged target closed immediately; untargeted `status:failure` runner crash issues older than 12 hours closed with resolution summary
|
|
27
27
|
- [ ] Summary posted listing all changes made
|
|
28
28
|
|
|
29
29
|
If any criterion cannot be met, stop immediately and log FAILURE with the reason.
|
|
@@ -41,7 +41,9 @@ If any criterion cannot be met, stop immediately and log FAILURE with the reason
|
|
|
41
41
|
|
|
42
42
|
1. **Stalled routine run and stale failure sweep**:
|
|
43
43
|
- **Stalled runs**: Check open issues with labels `routine-log` and `status:running`. If an issue has been in `status:running` for > 6 hours without updates, add label `status:failure`, remove `status:running`, add label `needs-attention`, and comment noting the runner timeout or crash.
|
|
44
|
-
- **Stale / untargeted failures**: Check open issues with labels `routine-log` and `status:failure`.
|
|
44
|
+
- **Stale / untargeted failures**: Check open issues with labels `routine-log` and `status:failure`. Reconcile and close them as follows:
|
|
45
|
+
- If the routine log references a target issue or PR that has already been closed or merged: close the routine-log issue **immediately** (0-hour delay) with comment "Closed: target issue/PR has been resolved.\n\n_Generated by [Antigravity](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID:-})_" and reason completed.
|
|
46
|
+
- If the routine log has no target issue or PR in its title or body (an untargeted runner crash): if older than 12 hours, close the routine-log issue with comment "Closed: untargeted routine failure superseded.\n\n_Generated by [Antigravity](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID:-})_" and reason completed.
|
|
45
47
|
2. **Orphaned-claim sweep**: Sweep assigned issues. If an issue meets the 3 stale-claim conditions in `ORCHESTRATION.md` (autowork claim comment, no open PR, comment > 6 hours old), re-read immediately before writing, unassign the dead owner, and post a release comment.
|
|
46
48
|
|
|
47
49
|
### Phase 2: Backlog Hygiene
|
|
@@ -63,6 +63,13 @@ export class LoopGuard {
|
|
|
63
63
|
recordAction(toolName, args, isError = false) {
|
|
64
64
|
if (this.trippedResult) return this.trippedResult;
|
|
65
65
|
|
|
66
|
+
const isStatusPolling =
|
|
67
|
+
toolName === 'manage_task' &&
|
|
68
|
+
(args?.Action === 'status' ||
|
|
69
|
+
args?.action === 'status' ||
|
|
70
|
+
args?.Action === 'list' ||
|
|
71
|
+
args?.action === 'list');
|
|
72
|
+
|
|
66
73
|
const hash = computeActionHash(toolName, args);
|
|
67
74
|
const record = {
|
|
68
75
|
toolName,
|
|
@@ -98,51 +105,64 @@ export class LoopGuard {
|
|
|
98
105
|
this.history.push(record);
|
|
99
106
|
|
|
100
107
|
// 2. Repetition Guard within sliding window
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
if (!isStatusPolling) {
|
|
109
|
+
const windowStart = Math.max(0, this.history.length - this.slidingWindowSize);
|
|
110
|
+
const currentWindow = this.history.slice(windowStart);
|
|
111
|
+
|
|
112
|
+
let repetitionCount = 0;
|
|
113
|
+
for (const item of currentWindow) {
|
|
114
|
+
if (item.hash === hash) {
|
|
115
|
+
repetitionCount++;
|
|
116
|
+
}
|
|
108
117
|
}
|
|
109
|
-
}
|
|
110
118
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
if (repetitionCount >= this.repetitionThreshold) {
|
|
120
|
+
return this.triggerTrip({
|
|
121
|
+
reason: 'repetition',
|
|
122
|
+
message: `Action repetition loop detected: tool '${toolName}' called ${repetitionCount} times with identical parameters within sliding window of ${this.slidingWindowSize}.`,
|
|
123
|
+
toolName,
|
|
124
|
+
actionHash: hash,
|
|
125
|
+
count: repetitionCount,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
// 3. Ping-Pong Guard: 3 consecutive alternating pairs (length 6)
|
|
122
131
|
const requiredPingPongLength = this.pingPongThreshold * 2;
|
|
123
132
|
if (this.history.length >= requiredPingPongLength) {
|
|
124
133
|
const pingPongSlice = this.history.slice(-requiredPingPongLength);
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
const hasStatusPolling = pingPongSlice.some(
|
|
135
|
+
(item) =>
|
|
136
|
+
item.toolName === 'manage_task' &&
|
|
137
|
+
(item.args?.Action === 'status' ||
|
|
138
|
+
item.args?.action === 'status' ||
|
|
139
|
+
item.args?.Action === 'list' ||
|
|
140
|
+
item.args?.action === 'list')
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (!hasStatusPolling) {
|
|
144
|
+
const hashA = pingPongSlice[0].hash;
|
|
145
|
+
const hashB = pingPongSlice[1].hash;
|
|
146
|
+
|
|
147
|
+
if (hashA !== hashB) {
|
|
148
|
+
let isPingPong = true;
|
|
149
|
+
for (let i = 0; i < requiredPingPongLength; i++) {
|
|
150
|
+
const expectedHash = i % 2 === 0 ? hashA : hashB;
|
|
151
|
+
if (pingPongSlice[i].hash !== expectedHash) {
|
|
152
|
+
isPingPong = false;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
135
155
|
}
|
|
136
|
-
}
|
|
137
156
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
if (isPingPong) {
|
|
158
|
+
return this.triggerTrip({
|
|
159
|
+
reason: 'ping_pong',
|
|
160
|
+
message: `Alternating ping-pong action loop detected: ${this.pingPongThreshold} consecutive alternating cycles between tools (A-B-A-B-A-B).`,
|
|
161
|
+
toolName,
|
|
162
|
+
actionHash: hash,
|
|
163
|
+
count: this.pingPongThreshold,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
146
166
|
}
|
|
147
167
|
}
|
|
148
168
|
}
|