Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
993cf7a
fix(tools): process queued messages when terminal command finishes
Sep 20, 2026
2d08001
fix(tools): gate queued-message drain on actual command submission
Sep 20, 2026
59d20fc
test(tools): cover execa retry working-directory validation gate
Sep 20, 2026
4765b92
fix(task): make queued-message drain awaitable and failure-aware
Sep 22, 2026
8ca6ec8
test(tools): cover interrupted and timeout queue drains
Sep 22, 2026
33aaa1e
test(tools): resolve processQueuedMessages mocks so success paths exe…
myk1yt Sep 27, 2026
fd9d422
fix(tools): separate command drain failures from shell-integration ha…
myk1yt Sep 27, 2026
6903902
fix(tools): gate background-completion drain on tool result publication
myk1yt Sep 27, 2026
cefd735
test(tools): cover queued-message drain rejection paths for patch cov…
myk1yt Sep 27, 2026
5183a1e
fix(task): serialize queued-message drains per task
myk1yt Sep 27, 2026
f2981f6
fix(task): retain queued messages until an ask consumes them
myk1yt Sep 27, 2026
6bc900e
fix(task): consume queued messages by identity with durable acks
myk1yt Sep 28, 2026
14125ee
test(tools): assert drain runs once after persisted output results
myk1yt Sep 28, 2026
4860a9c
fix(task): ack intercepted queued messages at every consuming ask
myk1yt Sep 28, 2026
8764212
fix(task): make queued-feedback persistence idempotent and drains can…
myk1yt Sep 28, 2026
fbb06bc
test(tools,assistant-message): cover queued-ack wrapper branches
myk1yt Sep 28, 2026
59f0e3f
fix(task): keep queued conversational messages out of approval asks
myk1yt Sep 28, 2026
844e2c2
fix(tools): settle the command publication signal on terminal errors
myk1yt Sep 28, 2026
31eb232
test(tools): strengthen the publication-signal settle test
myk1yt Sep 28, 2026
b6112c5
fix(task): keep unclaimed ask resolutions inert so auto-approval stil…
myk1yt Sep 28, 2026
43f1cd0
fix(tools): carry the publication outcome and never drain after a fai…
myk1yt Sep 28, 2026
b25c04a
fix(task): await the reconciled feedback row and make backoff cancell…
myk1yt Sep 28, 2026
3a5ae4d
test(task): pin the awaited reconciled-row update failure path
myk1yt Sep 28, 2026
7e8528d
Merge remote-tracking branch 'refs/remotes/upstream/main' into fix/93…
myk1yt Oct 3, 2026
af09e70
fix(task): gate queued-message drains against in-flight approval asks
myk1yt Oct 3, 2026
e7fbd33
fix(task): clear the pending drain tracker when the durable ack settles
myk1yt Oct 3, 2026
9cb2686
fix(tools): settle toolResultPublished on execa fallback and warning …
myk1yt Oct 3, 2026
e7613b1
fix(task): discard queued messages consumed by the truncation retry gate
myk1yt Oct 3, 2026
b38f196
fix(task): associate queued feedback rows before the history append
myk1yt Oct 3, 2026
8eba814
fix(task,assistant-message): ack queued feedback before it enters the…
myk1yt Oct 3, 2026
cf554c7
fix(webview): validate queued-message edits and handle condense rejec…
myk1yt Oct 3, 2026
306626b
test(task): widen the drain-spec emit test-double signature
myk1yt Oct 3, 2026
c14114d
fix(task): harden queued-drain guards (tracker release, gate arming, …
myk1yt Oct 3, 2026
3984e8b
fix(task): reconcile registered feedback rows through non-durable claims
myk1yt Oct 3, 2026
2fcf21d
fix(api): reject headless sendMessage when the task refuses delivery
myk1yt Oct 3, 2026
c42dd9c
fix(webview): surface refused edit resubmissions and condense failures
myk1yt Oct 3, 2026
f1d1a13
fix(task): keep queued messages out of failure-gate retry asks
myk1yt Oct 3, 2026
8be15bc
refactor(task): split ask into a gate wrapper and askImpl (CI diff bu…
myk1yt Oct 3, 2026
ebd2c53
Merge remote-tracking branch 'refs/remotes/upstream/main' into fix/93…
myk1yt Oct 3, 2026
4a2e675
fix(webview): post condenseTaskContextResponse on condense failure
myk1yt Oct 3, 2026
43a5ead
fix(api): contain rejected headless SendMessage inside the IPC boundary
myk1yt Oct 3, 2026
9a07b0c
fix(task): route queued-message edits through the task's pending subm…
myk1yt Oct 3, 2026
ed89a6e
fix(task): defer the ask-start queued claim to the handoff point
myk1yt Oct 3, 2026
f65eb12
fix(task): reserve a consumed drain submission through its durable ack
myk1yt Oct 3, 2026
d6d0b86
fix(task): track in-flight ask gates per ask
myk1yt Oct 3, 2026
a0f9770
fix(task): hand a consumed submission's ID only when its entry is res…
myk1yt Oct 3, 2026
e0b35ca
Merge remote-tracking branch 'origin/main' into fix/937-process-queue…
myk1yt Oct 5, 2026
db35152
fix(task): repair ask() abort re-check after main merge
myk1yt Oct 5, 2026
b3561ff
fix(task): persist queued edits made during a failed-save backoff
myk1yt Oct 5, 2026
6b801ce
test(tools): assert commandSubmitted in direct executeCommandInTermin…
myk1yt Oct 5, 2026
3e41611
chore: retrigger review-state reconciliation
myk1yt Oct 5, 2026
bc38df9
chore: re-run checks cancelled by infra
myk1yt Oct 5, 2026
d5872e3
chore: retrigger review-state reconciliation
myk1yt Oct 5, 2026
c7d0c44
Merge remote-tracking branch 'upstream/main' into fix/937-process-que…
myk1yt Oct 9, 2026
f6af7d8
fix(task): address CodeRabbit review on queued-message delivery
myk1yt Oct 9, 2026
8761ddc
Merge branch 'main' (upstream 4d14dbe1) into fix/937-process-queued-m…
myk1yt Oct 30, 2026
0bbba8d
[Fix] Close the queued-drain approval and delivery defects in #937
myk1yt Oct 30, 2026
52d9e79
[Fix] Keep drain retention through a failed durable ack and bind queu…
myk1yt Oct 30, 2026
bd2769f
[Fix] Append queued feedback once and bind mention cwd before the await
myk1yt Oct 30, 2026
eaa9c82
[Fix] Keep the queued-feedback row record consistent when say rejects
myk1yt Oct 30, 2026
e67d501
[Fix] Re-append queued feedback when a rewind removed its row
myk1yt Oct 30, 2026
95a13d7
test(queue): pin the ask-response cwd capture and the message-id keying
myk1yt Oct 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/types/src/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export interface TaskLike {

approveAsk(options?: { text?: string; images?: string[] }): void
denyAsk(options?: { text?: string; images?: string[] }): void
submitUserMessage(text: string, images?: string[], mode?: string, providerProfile?: string): Promise<void>
submitUserMessage(text: string, images?: string[], mode?: string, providerProfile?: string): Promise<boolean>
abortTask(): Promise<void>
}

Expand Down
32 changes: 18 additions & 14 deletions src/core/message-queue/MessageQueueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,18 @@ export class MessageQueueService extends EventEmitter<QueueEvents> {
return true
}

public dequeueMessage(): QueuedMessage | undefined {
const index = this._messages.findIndex((message) => !this.claimedMessageIds.has(message.id))
if (index === -1) {
return undefined
}
const [message] = this._messages.splice(index, 1)
this.emit("stateChanged", this._messages)
return message
}

public claimNextMessage(): QueuedMessage | undefined {
const message = this._messages.find((candidate) => !this.claimedMessageIds.has(candidate.id))
/**
* Claims the first message that is neither already claimed nor listed in
* `excludeIds`.
*
* `excludeIds` lets a consumer that must not take certain messages skip them
* without removing them: an execution-gating ask passes the IDs a drain has
* already submitted so stale feedback can never stand in for an approval.
*/
public claimNextMessage(excludeIds?: ReadonlySet<string>): QueuedMessage | undefined {
const message = this._messages.find(
(candidate) => !this.claimedMessageIds.has(candidate.id) && !excludeIds?.has(candidate.id),
)
if (message) {
this.claimedMessageIds.add(message.id)
}
Expand Down Expand Up @@ -115,9 +115,13 @@ export class MessageQueueService extends EventEmitter<QueueEvents> {
* report a non-empty queue. Consumers that are about to take a message need
* this instead: it answers "is there anything I may take", which is false
* when every remaining message is already spoken for.
*
* `excludeIds` mirrors `claimNextMessage`: a message the caller may not take
* is not available, so an execution-gating ask does not report availability
* for a message only a conversational ask may consume.
*/
public hasUnclaimed(): boolean {
return this._messages.some((message) => !this.claimedMessageIds.has(message.id))
public hasUnclaimed(excludeIds?: ReadonlySet<string>): boolean {
return this._messages.some((message) => !this.claimedMessageIds.has(message.id) && !excludeIds?.has(message.id))
}

public dispose(): void {
Expand Down
30 changes: 29 additions & 1 deletion src/core/message-queue/__tests__/MessageQueueService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe("MessageQueueService claims", () => {

expect(queue.claimNextMessage()).toEqual(first)
expect(queue.claimNextMessage()).toEqual(second)
expect(queue.dequeueMessage()).toBeUndefined()
expect(queue.claimNextMessage()).toBeUndefined()
expect(queue.messages).toEqual([first, second])
})

Expand Down Expand Up @@ -67,4 +67,32 @@ describe("MessageQueueService claims", () => {
queue.releaseMessage(message.id)
expect(queue.hasUnclaimed()).toBe(true)
})

it("skips excluded messages without claiming or removing them", () => {
const queue = new MessageQueueService()
const retained = queue.addMessage("retained by a drain")!
const fresh = queue.addMessage("fresh")!
const excluded = new Set([retained.id])

// Availability must agree with the claim: the excluded message is not
// available to this consumer even though the queue holds it.
expect(queue.hasUnclaimed(excluded)).toBe(true)
expect(queue.claimNextMessage(excluded)).toEqual(fresh)

// The excluded message stays queued and unclaimed, so a consumer allowed
// to take it (a conversational ask) still can.
expect(queue.messages).toEqual([retained, fresh])
expect(queue.hasUnclaimed(excluded)).toBe(false)
expect(queue.claimNextMessage()).toEqual(retained)
})

it("reports no availability when every message is excluded", () => {
const queue = new MessageQueueService()
const only = queue.addMessage("only")!
const excluded = new Set([only.id])

expect(queue.hasUnclaimed(excluded)).toBe(false)
expect(queue.claimNextMessage(excluded)).toBeUndefined()
expect(queue.messages).toEqual([only])
})
})
Loading
Loading