Skip to content

Commit ea7c623

Browse files
authored
Merge branch 'main' into repo-assist/feat-sumby-averageby-2026-03-8d8d6841f6117a18
2 parents 4846317 + c4fbcf6 commit ea7c623

File tree

6 files changed

+184
-24
lines changed

6 files changed

+184
-24
lines changed

.github/workflows/repo-assist.lock.yml

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/repo-assist.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ safe-outputs:
5858
add-labels:
5959
allowed: [bug, enhancement, "help wanted", "good first issue", "spam", "off topic", documentation, question, duplicate, wontfix, "needs triage", "needs investigation", "breaking change", performance, security, refactor]
6060
max: 30
61-
target: "*"
61+
target: "*"
6262
remove-labels:
6363
allowed: [bug, enhancement, "help wanted", "good first issue", "spam", "off topic", documentation, question, duplicate, wontfix, "needs triage", "needs investigation", "breaking change", performance, security, refactor]
6464
max: 5
65-
target: "*"
65+
target: "*"
6666

6767
tools:
6868
web-fetch:
@@ -150,8 +150,7 @@ steps:
150150
json.dump(result, f, indent=2)
151151
EOF
152152
153-
source: githubnext/agentics/workflows/repo-assist.md@ec7d342403c9912c87320110f8822a8fbb817a0c
154-
engine: copilot
153+
source: githubnext/agentics/workflows/repo-assist.md@4ee2ca4faa30612b9ed0d207472068f5efc9ecf3
155154
---
156155

157156
# Repo Assist
@@ -160,7 +159,7 @@ engine: copilot
160159

161160
Take heed of **instructions**: "${{ steps.sanitized.outputs.text }}"
162161

163-
If these are non-empty (not ""), then you have been triggered via `/repo-assist <instructions>`. Follow the user's instructions instead of the normal scheduled workflow. Focus exclusively on those instructions. Apply all the same guidelines (read AGENTS.md, run formatters/linters/tests, be polite, use AI disclosure). Skip the weighted task selection and Task 11 reporting, and instead directly do what the user requested. If no specific instructions were provided (empty or blank), proceed with the normal scheduled workflow below.
162+
If these are non-empty (not ""), then you have been triggered via `/repo-assist <instructions>`. Follow the user's instructions instead of the normal scheduled workflow. Focus exclusively on those instructions. Apply all the same guidelines (read AGENTS.md, run formatters/linters/tests, be polite, use AI disclosure). Skip the weighted task selection and Task 11 reporting, and instead directly do what the user requested. If no specific instructions were provided (empty or blank), proceed with the normal scheduled workflow below.
164163

165164
Then exit - do not run the normal workflow after completing the instructions.
166165

@@ -231,7 +230,7 @@ Update memory with labels applied and cursor position.
231230
1. Review issues labelled `bug`, `help wanted`, or `good first issue`, plus any identified as fixable during investigation.
232231
2. For each fixable issue:
233232
a. Check memory — skip if you've already tried and the attempt is still open. Never create duplicate PRs.
234-
b. Create a fresh branch off `main`: `repo-assist/fix-issue-<N>-<desc>`.
233+
b. Create a fresh branch off the default branch of the repository: `repo-assist/fix-issue-<N>-<desc>`.
235234
c. Implement a minimal, surgical fix. Do not refactor unrelated code.
236235
d. **Build and test (required)**: do not create a PR if the build fails or tests fail due to your changes. If tests fail due to infrastructure, create the PR but document it.
237236
e. Add a test for the bug if feasible; re-run tests.
@@ -256,7 +255,7 @@ Study the codebase and make clearly beneficial, low-risk improvements. **Be high
256255

257256
Good candidates: code clarity and readability, removing dead code, API usability, documentation gaps, reducing duplication.
258257

259-
Check memory for already-submitted ideas; do not re-propose them. Create a fresh branch `repo-assist/improve-<desc>` off `main`, implement the improvement, build and test (same requirements as Task 3), then create a draft PR with AI disclosure, rationale, and Test Status section. If not ready to implement, file an issue instead. Update memory.
258+
Check memory for already-submitted ideas; do not re-propose them. Create a fresh branch `repo-assist/improve-<desc>` off the default branch of the repository, implement the improvement, build and test (same requirements as Task 3), then create a draft PR with AI disclosure, rationale, and Test Status section. If not ready to implement, file an issue instead. Update memory.
260259

261260
### Task 6: Maintain Repo Assist PRs
262261

@@ -297,7 +296,7 @@ Maintain a single open issue titled `[Repo Assist] Monthly Activity {YYYY}-{MM}`
297296

298297
## Suggested Actions for Maintainer
299298

300-
**Comprehensive list** of all pending actions requiring maintainer attention (excludes items already actioned and checked off).
299+
**Comprehensive list** of all pending actions requiring maintainer attention (excludes items already actioned and checked off).
301300
- Reread the issue you're updating before you update it - there may be new checkbox adjustments since your last update that require you to adjust the suggested actions.
302301
- List **all** the comments, PRs, and issues that need attention
303302
- Exclude **all** items that have either
@@ -363,4 +362,4 @@ Maintain a single open issue titled `[Repo Assist] Monthly Activity {YYYY}-{MM}`
363362
- **Systematic**: use the backlog cursor to process oldest issues first over successive runs. Do not stop early.
364363
- **Release preparation**: use your judgement on each run to assess whether a release is warranted (significant unreleased changes, changelog out of date). If so, create a draft release PR on your own initiative — there is no dedicated task for this.
365364
- **Quality over quantity**: noise erodes trust. Do nothing rather than add low-value output.
366-
- **Bias toward action**: While avoiding spam, actively seek ways to contribute value within the two selected tasks. A "no action" run should be genuinely exceptional.
365+
- **Bias toward action**: While avoiding spam, actively seek ways to contribute value within the two selected tasks. A "no action" run should be genuinely exceptional.

release-notes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Release notes:
66
- adds TaskSeq.scan and TaskSeq.scanAsync, #289
77
- adds TaskSeq.pairwise, #289
88
- adds TaskSeq.sum, sumBy, sumByAsync, average, averageBy, averageByAsync
9+
- fixes: CancellationToken passed to GetAsyncEnumerator is now honored in MoveNextAsync, #179
910

1011
0.4.0
1112
- overhaul all doc comments, add exceptions, improve IDE quick-info experience, #136, #220, #234

src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<Compile Include="TaskSeq.Do.Tests.fs" />
6464
<Compile Include="TaskSeq.Let.Tests.fs" />
6565
<Compile Include="TaskSeq.Using.Tests.fs" />
66+
<Compile Include="TaskSeq.CancellationToken.Tests.fs" />
6667
</ItemGroup>
6768

6869
<ItemGroup>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
module TaskSeq.Tests.CancellationToken
2+
3+
open System
4+
open System.Threading
5+
open System.Threading.Tasks
6+
7+
open Xunit
8+
open FsUnit.Xunit
9+
10+
open FSharp.Control
11+
12+
/// An infinite taskSeq that yields 1 forever
13+
let private infiniteOnes () = taskSeq {
14+
while true do
15+
yield 1
16+
}
17+
18+
/// A finite taskSeq with a few items
19+
let private fiveItems () = taskSeq {
20+
yield 1
21+
yield 2
22+
yield 3
23+
yield 4
24+
yield 5
25+
}
26+
27+
module Cancellation =
28+
29+
[<Fact>]
30+
let ``GetAsyncEnumerator with pre-cancelled token: first MoveNextAsync throws OperationCanceledException`` () = task {
31+
use cts = new CancellationTokenSource()
32+
cts.Cancel()
33+
use enum = (infiniteOnes ()).GetAsyncEnumerator(cts.Token)
34+
35+
fun () -> enum.MoveNextAsync().AsTask() |> Task.ignore
36+
|> should throwAsync typeof<OperationCanceledException>
37+
}
38+
39+
[<Fact>]
40+
let ``GetAsyncEnumerator with pre-cancelled token: MoveNextAsync on finite seq also throws`` () = task {
41+
use cts = new CancellationTokenSource()
42+
cts.Cancel()
43+
use enum = (fiveItems ()).GetAsyncEnumerator(cts.Token)
44+
45+
fun () -> enum.MoveNextAsync().AsTask() |> Task.ignore
46+
|> should throwAsync typeof<OperationCanceledException>
47+
}
48+
49+
[<Fact>]
50+
let ``GetAsyncEnumerator with non-cancelled token: iteration proceeds normally`` () = task {
51+
use cts = new CancellationTokenSource()
52+
use enum = (fiveItems ()).GetAsyncEnumerator(cts.Token)
53+
let mutable count = 0
54+
let mutable canContinue = true
55+
56+
while canContinue do
57+
let! hasNext = enum.MoveNextAsync()
58+
59+
if hasNext then count <- count + 1 else canContinue <- false
60+
61+
count |> should equal 5
62+
}
63+
64+
[<Fact>]
65+
let ``GetAsyncEnumerator with CancellationToken.None: iteration proceeds normally`` () = task {
66+
use enum = (fiveItems ()).GetAsyncEnumerator(CancellationToken.None)
67+
let mutable count = 0
68+
let mutable canContinue = true
69+
70+
while canContinue do
71+
let! hasNext = enum.MoveNextAsync()
72+
73+
if hasNext then count <- count + 1 else canContinue <- false
74+
75+
count |> should equal 5
76+
}
77+
78+
[<Fact>]
79+
let ``Token cancelled after partial iteration: next MoveNextAsync throws OperationCanceledException`` () = task {
80+
use cts = new CancellationTokenSource()
81+
use enum = (fiveItems ()).GetAsyncEnumerator(cts.Token)
82+
83+
// Consume first two items normally
84+
let! _ = enum.MoveNextAsync()
85+
let! _ = enum.MoveNextAsync()
86+
87+
// Cancel the token
88+
cts.Cancel()
89+
90+
// Next call should throw
91+
fun () -> enum.MoveNextAsync().AsTask() |> Task.ignore
92+
|> should throwAsync typeof<OperationCanceledException>
93+
}
94+
95+
[<Fact>]
96+
let ``Infinite sequence with pre-cancelled token: throws immediately without consuming any items`` () = task {
97+
use cts = new CancellationTokenSource()
98+
cts.Cancel()
99+
let mutable itemsConsumed = 0
100+
101+
let seq = taskSeq {
102+
while true do
103+
itemsConsumed <- itemsConsumed + 1
104+
yield itemsConsumed
105+
}
106+
107+
use enum = seq.GetAsyncEnumerator(cts.Token)
108+
109+
fun () -> enum.MoveNextAsync().AsTask() |> Task.ignore
110+
|> should throwAsync typeof<OperationCanceledException>
111+
112+
// The body should not have run (cancellation checked before advancing state machine)
113+
itemsConsumed |> should equal 0
114+
}
115+
116+
[<Fact>]
117+
let ``Token cancelled mid-iteration of infinite sequence terminates with OperationCanceledException`` () = task {
118+
use cts = new CancellationTokenSource()
119+
use enum = (infiniteOnes ()).GetAsyncEnumerator(cts.Token)
120+
121+
// Iterate a few steps without cancellation
122+
for _ in 1..5 do
123+
let! hasNext = enum.MoveNextAsync()
124+
hasNext |> should be True
125+
126+
// Now cancel
127+
cts.Cancel()
128+
129+
// Next call should throw
130+
fun () -> enum.MoveNextAsync().AsTask() |> Task.ignore
131+
|> should throwAsync typeof<OperationCanceledException>
132+
}
133+
134+
[<Fact>]
135+
let ``Multiple enumerators of same sequence respect independent cancellation tokens`` () = task {
136+
let source = fiveItems ()
137+
use cts1 = new CancellationTokenSource()
138+
use cts2 = new CancellationTokenSource()
139+
140+
// Cancel only the first token
141+
cts1.Cancel()
142+
143+
use enum1 = source.GetAsyncEnumerator(cts1.Token)
144+
use enum2 = source.GetAsyncEnumerator(cts2.Token)
145+
146+
// enum1 should throw (cancelled)
147+
fun () -> enum1.MoveNextAsync().AsTask() |> Task.ignore
148+
|> should throwAsync typeof<OperationCanceledException>
149+
150+
// enum2 should work normally (not cancelled)
151+
let! hasNext = enum2.MoveNextAsync()
152+
hasNext |> should be True
153+
enum2.Current |> should equal 1
154+
}

src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ and [<NoComparison; NoEquality>] TaskSeq<'Machine, 'T
242242
Debug.logInfo "at MoveNextAsync: normal resumption scenario"
243243

244244
let data = this._machine.Data
245+
246+
// Honor the cancellation token passed to GetAsyncEnumerator (fixes #179).
247+
// ThrowIfCancellationRequested() is a no-op for CancellationToken.None.
248+
data.cancellationToken.ThrowIfCancellationRequested()
249+
245250
data.promiseOfValueOrEnd.Reset()
246251
let mutable ts = this
247252

0 commit comments

Comments
 (0)