From 99296f262e3f0b1aa6c7cc4d141bf7415a11d05d Mon Sep 17 00:00:00 2001 From: Zbigniew Sobiecki Date: Wed, 6 May 2026 11:38:39 +0000 Subject: [PATCH 1/2] fix(router): serialize backlog-manager + guard MoveWorkItem against parallel races MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live incident 2026-05-06 (ucho): two `backlog-manager` runs auto-chained in parallel — one from MNG-536's PR-merge, one from MNG-537's splitting auto-chain — both scanned the same backlog, both selected MNG-538, both moved it to TODO. The two `pm:status-changed` webhooks each fired the implementation trigger, producing duplicate PRs (#287 and #288). Two complementary defenses: 1. **Project-singleton lock for `backlog-manager`** (primary): the per-(projectId, workItemId, agentType) lock did NOT serialize the two runs because their nominal workItemId differed (MNG-536 vs MNG-537). `work-item-lock.ts` now collapses workItemId to a sentinel for project-singleton agents — both in-memory and the DB count — so a second backlog-manager dispatch on the same project is blocked while the first is in flight. 2. **MoveWorkItem `expectedSourceState` guard** (defense-in-depth): if a second run somehow proceeds (lock TTL expiry, restart, future regression), the gadget refuses to move an item whose current status doesn't match the caller's expectation, and treats already-at- destination as a silent no-op. The backlog-manager prompt now instructs the agent to pass `expectedSourceState: <%= backlogSourceLabel %>` on every move-to-TODO. The label is provider-correct (Trello list ID, JIRA/Linear status name) and case-insensitive matched. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/agents/prompts/index.ts | 14 +++ .../prompts/templates/backlog-manager.eta | 2 +- src/agents/shared/promptContext.ts | 10 ++ src/cli/pm/move-work-item.ts | 1 + src/gadgets/pm/core/moveWorkItem.ts | 34 ++++++ src/gadgets/pm/definitions.ts | 15 +++ src/router/work-item-lock.ts | 38 +++++- .../unit/gadgets/pm/core/moveWorkItem.test.ts | 113 ++++++++++++++++++ tests/unit/router/work-item-lock.test.ts | 63 ++++++++++ 9 files changed, 285 insertions(+), 5 deletions(-) diff --git a/src/agents/prompts/index.ts b/src/agents/prompts/index.ts index 511d42b75..7617c6ba1 100644 --- a/src/agents/prompts/index.ts +++ b/src/agents/prompts/index.ts @@ -43,6 +43,14 @@ export interface PromptContext { // PM list/column IDs backlogListId?: string; + /** + * Value to pass as `expectedSourceState` to the MoveWorkItem gadget + * when moving an item out of BACKLOG. Per-provider: + * - Trello: backlog list ID (matches `WorkItem.status: card.idList`) + * - JIRA: "Backlog" status name + * - Linear: "Backlog" workflow-state name + */ + backlogSourceLabel?: string; todoListId?: string; inProgressListId?: string; inReviewListId?: string; @@ -313,6 +321,12 @@ export function getTemplateVariables(): Array<{ { name: 'workItemNounPluralCap', group: 'PM', description: 'Cards or Issues' }, { name: 'pmName', group: 'PM', description: 'Trello or JIRA' }, { name: 'backlogListId', group: 'PM Lists', description: 'Backlog list/column ID' }, + { + name: 'backlogSourceLabel', + group: 'PM Lists', + description: + 'Value to pass as MoveWorkItem `expectedSourceState` for items in BACKLOG (provider-correct: Trello list ID, JIRA/Linear status name).', + }, { name: 'todoListId', group: 'PM Lists', description: 'TODO list/column ID' }, { name: 'inProgressListId', group: 'PM Lists', description: 'In Progress list/column ID' }, { name: 'inReviewListId', group: 'PM Lists', description: 'In Review list/column ID' }, diff --git a/src/agents/prompts/templates/backlog-manager.eta b/src/agents/prompts/templates/backlog-manager.eta index 344a563d3..63c90f60c 100644 --- a/src/agents/prompts/templates/backlog-manager.eta +++ b/src/agents/prompts/templates/backlog-manager.eta @@ -59,7 +59,7 @@ When the active pipeline has capacity: - <%= it.workItemNounPluralCap || 'Cards' %> that don't reference incomplete work <% if ((it.maxInFlightItems ?? 1) > 1) { %> - **Conflict Awareness**: When selecting multiple <%= it.workItemNounPlural || 'cards' %>, review in-flight work descriptions to minimize file-level conflicts between simultaneously active <%= it.workItemNounPlural || 'cards' %>. Prefer <%= it.workItemNounPlural || 'cards' %> that touch different areas of the codebase. <% } %>5. **Post a comment** on each selected <%= it.workItemNoun || 'card' %> explaining the selection -6. **Move the selected <%= it.workItemNoun || 'card' %>(s)** using `MoveWorkItem` with the TODO list ID as destination +6. **Move the selected <%= it.workItemNoun || 'card' %>(s)** using `MoveWorkItem` with the TODO list ID as destination AND `expectedSourceState: <%= it.backlogSourceLabel || 'Backlog' %>`. The `expectedSourceState` guard is **mandatory** — it aborts the move if a parallel run already moved the <%= it.workItemNoun || 'card' %> out of BACKLOG, preventing duplicate downstream implementation runs. ## Comment Format diff --git a/src/agents/shared/promptContext.ts b/src/agents/shared/promptContext.ts index 72412f976..150f92780 100644 --- a/src/agents/shared/promptContext.ts +++ b/src/agents/shared/promptContext.ts @@ -9,6 +9,16 @@ function getListIds(project: ProjectConfig) { const linearConfig = getLinearConfig(project); return { + // Value the agent should pass as `expectedSourceState` when moving an + // item out of BACKLOG. Aligned with what `WorkItem.status` returns + // per provider: + // - Trello: `WorkItem.status` is the list ID → pass list ID. + // - JIRA: `WorkItem.status` is the status name → pass status name. + // - Linear: `WorkItem.status` is the workflow-state name → pass the + // state name. Linear teams default to literal "Backlog"; + // customized teams can still pass it via the prompt path + // since the gadget guard is case-insensitive. + backlogSourceLabel: trelloConfig?.lists?.backlog ?? jiraConfig?.statuses?.backlog ?? 'Backlog', backlogListId: trelloConfig?.lists?.backlog ?? jiraConfig?.statuses?.backlog ?? linearConfig?.teamId, todoListId: diff --git a/src/cli/pm/move-work-item.ts b/src/cli/pm/move-work-item.ts index 0702ed772..327768e43 100644 --- a/src/cli/pm/move-work-item.ts +++ b/src/cli/pm/move-work-item.ts @@ -6,5 +6,6 @@ export default createCLICommand(moveWorkItemDef, async (params) => { return moveWorkItem({ workItemId: params.workItemId as string, destination: params.destination as string, + expectedSourceState: params.expectedSourceState as string | undefined, }); }); diff --git a/src/gadgets/pm/core/moveWorkItem.ts b/src/gadgets/pm/core/moveWorkItem.ts index faf4bb17e..a805b930e 100644 --- a/src/gadgets/pm/core/moveWorkItem.ts +++ b/src/gadgets/pm/core/moveWorkItem.ts @@ -3,10 +3,44 @@ import { getPMProvider } from '../../../pm/index.js'; export interface MoveWorkItemParams { workItemId: string; destination: string; + /** + * Optional pre-move guard. When provided, the gadget fetches the work + * item's current status and refuses to move unless it matches (case- + * insensitive). Defends against parallel-agent races — e.g. a second + * backlog-manager run trying to move an item that's already been + * moved out of BACKLOG by a sibling run (incident 2026-05-06, MNG-538). + * + * If the current status already equals `destination`, the move is + * skipped as a no-op (idempotent). + */ + expectedSourceState?: string; +} + +function normalizeStatus(s: string | undefined): string { + return (s ?? '').trim().toLowerCase(); } export async function moveWorkItem(params: MoveWorkItemParams): Promise { try { + if (params.expectedSourceState !== undefined) { + const provider = getPMProvider(); + const current = await provider.getWorkItem(params.workItemId); + const currentStatus = normalizeStatus(current.status); + const expected = normalizeStatus(params.expectedSourceState); + const destination = normalizeStatus(params.destination); + + if (currentStatus && currentStatus === destination) { + return `Work item ${params.workItemId} already in destination state '${current.status}' — no-op`; + } + + if (currentStatus !== expected) { + return `Aborted: work item ${params.workItemId} is in '${current.status ?? 'unknown'}', expected '${params.expectedSourceState}' (likely already moved by a parallel agent — skipping to avoid duplicate downstream work)`; + } + + await provider.moveWorkItem(params.workItemId, params.destination); + return `Work item ${params.workItemId} moved to ${params.destination} successfully`; + } + await getPMProvider().moveWorkItem(params.workItemId, params.destination); return `Work item ${params.workItemId} moved to ${params.destination} successfully`; } catch (error) { diff --git a/src/gadgets/pm/definitions.ts b/src/gadgets/pm/definitions.ts index aac397dc6..839a52eb4 100644 --- a/src/gadgets/pm/definitions.ts +++ b/src/gadgets/pm/definitions.ts @@ -201,6 +201,12 @@ export const moveWorkItemDef: ToolDefinition = { describe: 'Destination — Trello list ID or JIRA status name', required: true, }, + expectedSourceState: { + type: 'string', + describe: + 'Optional pre-move guard. If set, the move only proceeds when the work item\'s current status matches this value (case-insensitive). Use this whenever the move depends on a prior pipeline-state assumption (e.g. "BACKLOG" before moving to TODO) to defend against parallel-agent races. If the work item is already in the destination state, the move is skipped as a no-op.', + required: false, + }, }, examples: [ { @@ -210,6 +216,15 @@ export const moveWorkItemDef: ToolDefinition = { }, comment: 'Move a Trello card to a different list', }, + { + params: { + workItemId: 'MNG-538', + destination: 'TODO_LIST_ID', + expectedSourceState: 'Backlog', + }, + comment: + 'Backlog-manager moving a freshly-picked item to TODO — guarded so a parallel run that already moved it cannot duplicate the move.', + }, ], }; diff --git a/src/router/work-item-lock.ts b/src/router/work-item-lock.ts index 67d37ddce..6bdc7d80f 100644 --- a/src/router/work-item-lock.ts +++ b/src/router/work-item-lock.ts @@ -21,6 +21,26 @@ export const MAX_SAME_TYPE_PER_WORK_ITEM = 1; const TTL_MS = 30 * 60 * 1000; // 30 minutes +/** + * Agent types that act on the project's whole backlog rather than a single + * work item. Two parallel runs MUST serialize at the project level, even + * when their nominal `workItemId` differs (e.g. backlog-manager auto-chained + * from MNG-536's PR merge AND from MNG-537's splitting completion both scan + * the same backlog and can pick the same item — live incident 2026-05-06, + * MNG-538 produced PRs #287 and #288). + * + * For these agents the lock key collapses workItemId to a sentinel, and + * the DB count omits workItemId so all rows for the agent type in the + * project are counted together. + */ +const PROJECT_SINGLETON_AGENTS = new Set(['backlog-manager']); + +const SINGLETON_WORK_ITEM_KEY = '*'; + +function isProjectSingletonAgent(agentType: string): boolean { + return PROJECT_SINGLETON_AGENTS.has(agentType); +} + interface EnqueuedEntry { timestamp: number; count: number; @@ -28,8 +48,12 @@ interface EnqueuedEntry { const enqueuedMap = new Map(); +function effectiveWorkItemId(workItemId: string, agentType: string): string { + return isProjectSingletonAgent(agentType) ? SINGLETON_WORK_ITEM_KEY : workItemId; +} + function makeKey(projectId: string, workItemId: string, agentType: string): string { - return `${projectId}:${workItemId}:${agentType}`; + return `${projectId}:${effectiveWorkItemId(workItemId, agentType)}:${agentType}`; } /** @@ -78,15 +102,21 @@ export async function isWorkItemLocked( }; } - // DB check — same-type only, ignore runs older than 2× worker timeout + // DB check — same-type only, ignore runs older than 2× worker timeout. + // For project-singleton agents, omit workItemId so all rows for the + // agent type within the project count toward the limit. const maxAgeMs = 2 * routerConfig.workerTimeoutMs; - const dbSameType = await countActiveRuns({ projectId, workItemId, agentType, maxAgeMs }); + const dbQuery = isProjectSingletonAgent(agentType) + ? { projectId, agentType, maxAgeMs } + : { projectId, workItemId, agentType, maxAgeMs }; + const dbSameType = await countActiveRuns(dbQuery); const effectiveSameType = Math.max(dbSameType, inMemorySameType); if (effectiveSameType >= MAX_SAME_TYPE_PER_WORK_ITEM) { + const scope = isProjectSingletonAgent(agentType) ? 'project-singleton' : 'same-type'; return { locked: true, - reason: `same-type: ${dbSameType} running, ${inMemorySameType} enqueued (max ${MAX_SAME_TYPE_PER_WORK_ITEM} per type)`, + reason: `${scope}: ${dbSameType} running, ${inMemorySameType} enqueued (max ${MAX_SAME_TYPE_PER_WORK_ITEM} per type)`, }; } diff --git a/tests/unit/gadgets/pm/core/moveWorkItem.test.ts b/tests/unit/gadgets/pm/core/moveWorkItem.test.ts index 6a28a5802..395bb3a76 100644 --- a/tests/unit/gadgets/pm/core/moveWorkItem.test.ts +++ b/tests/unit/gadgets/pm/core/moveWorkItem.test.ts @@ -44,4 +44,117 @@ describe('moveWorkItem', () => { expect(result).toBe('Error moving work item: network timeout'); }); + + // ── expectedSourceState guard ──────────────────────────────────────────── + // Defends against parallel agents (e.g. two backlog-manager runs racing on + // the same backlog) by verifying the work item is in the expected source + // state before mutating. Live incident 2026-05-06 (MNG-538): a second + // backlog-manager run moved MNG-538 from In Progress back to TODO, + // triggering a duplicate implementation run (PRs #287, #288). + describe('expectedSourceState guard', () => { + const baseItem = { + id: 'MNG-538', + title: 'Persistence for tool confirmation policy', + description: '', + url: 'https://linear.app/mongrel/issue/MNG-538', + labels: [], + }; + + it('proceeds with move when current status matches expectedSourceState', async () => { + mockProvider.getWorkItem.mockResolvedValue({ + ...baseItem, + status: 'Backlog', + }); + mockProvider.moveWorkItem.mockResolvedValue(undefined); + + const result = await moveWorkItem({ + workItemId: 'MNG-538', + destination: 'todo-state-id', + expectedSourceState: 'Backlog', + }); + + expect(mockProvider.getWorkItem).toHaveBeenCalledWith('MNG-538'); + expect(mockProvider.moveWorkItem).toHaveBeenCalledWith('MNG-538', 'todo-state-id'); + expect(result).toContain('moved'); + }); + + it('aborts move when current status differs from expectedSourceState', async () => { + mockProvider.getWorkItem.mockResolvedValue({ + ...baseItem, + status: 'In Progress', + }); + + const result = await moveWorkItem({ + workItemId: 'MNG-538', + destination: 'todo-state-id', + expectedSourceState: 'Backlog', + }); + + expect(mockProvider.moveWorkItem).not.toHaveBeenCalled(); + expect(result).toMatch(/Aborted|aborted|skipped/); + expect(result).toContain('In Progress'); + expect(result).toContain('Backlog'); + }); + + it('matches expectedSourceState case-insensitively (Linear vs Trello casing drift)', async () => { + mockProvider.getWorkItem.mockResolvedValue({ + ...baseItem, + status: 'BACKLOG', + }); + mockProvider.moveWorkItem.mockResolvedValue(undefined); + + const result = await moveWorkItem({ + workItemId: 'MNG-538', + destination: 'todo-state-id', + expectedSourceState: 'backlog', + }); + + expect(mockProvider.moveWorkItem).toHaveBeenCalled(); + expect(result).toContain('moved'); + }); + + it('skips silently when current status is already the destination (idempotency)', async () => { + // expectedSourceState matches but current status equals destination — + // rare race where a parallel agent already moved the item. Treat as + // no-op rather than firing a redundant Linear API call. + mockProvider.getWorkItem.mockResolvedValue({ + ...baseItem, + status: 'Todo', + }); + + const result = await moveWorkItem({ + workItemId: 'MNG-538', + destination: 'Todo', + expectedSourceState: 'Backlog', + }); + + expect(mockProvider.moveWorkItem).not.toHaveBeenCalled(); + expect(result).toMatch(/already|no-op|aborted/i); + }); + + it('does NOT call getWorkItem when expectedSourceState is omitted (back-compat)', async () => { + mockProvider.moveWorkItem.mockResolvedValue(undefined); + + await moveWorkItem({ + workItemId: 'card1', + destination: 'list2', + }); + + expect(mockProvider.getWorkItem).not.toHaveBeenCalled(); + expect(mockProvider.moveWorkItem).toHaveBeenCalledWith('card1', 'list2'); + }); + + it('returns a structured error if getWorkItem throws', async () => { + mockProvider.getWorkItem.mockRejectedValue(new Error('API down')); + + const result = await moveWorkItem({ + workItemId: 'MNG-538', + destination: 'todo-state-id', + expectedSourceState: 'Backlog', + }); + + expect(mockProvider.moveWorkItem).not.toHaveBeenCalled(); + expect(result).toContain('Error'); + }); + }); }); diff --git a/tests/unit/router/work-item-lock.test.ts b/tests/unit/router/work-item-lock.test.ts index 19eb5c76e..cf87ec439 100644 --- a/tests/unit/router/work-item-lock.test.ts +++ b/tests/unit/router/work-item-lock.test.ts @@ -160,4 +160,67 @@ describe('work-item-lock', () => { // No total cap — 'debug' same-type is 0, so unlocked expect(result.locked).toBe(false); }); + + // ── Project-singleton agents (backlog-manager) ────────────────────────── + // Backlog-manager scans the whole project backlog and selects the next + // item to move to TODO. Two parallel runs (e.g. one chained from + // MNG-536's PR merge, one from MNG-537's splitting auto-chain) both + // scan the same backlog and both can pick the same item — producing + // duplicate downstream implementation runs (live incident 2026-05-06, + // MNG-538: PRs #287 and #288). Per-(projectId, workItemId, agentType) + // locking did NOT serialize them because workItemId differed (MNG-536 + // vs MNG-537). The fix collapses workItemId for project-singleton + // agents so the lock is per-(projectId, agentType). + describe('project-singleton agents (backlog-manager)', () => { + it('blocks a second backlog-manager on a different workItemId in the same project', async () => { + markWorkItemEnqueued('proj1', 'MNG-536', 'backlog-manager'); + const result = await isWorkItemLocked('proj1', 'MNG-537', 'backlog-manager'); + expect(result.locked).toBe(true); + expect(result.reason).toMatch(/singleton|same-type/i); + }); + + it('does NOT block backlog-manager across different projects', async () => { + markWorkItemEnqueued('proj1', 'MNG-536', 'backlog-manager'); + const result = await isWorkItemLocked('proj2', 'MNG-536', 'backlog-manager'); + expect(result.locked).toBe(false); + }); + + it('clearWorkItemEnqueued releases backlog-manager regardless of workItemId', async () => { + markWorkItemEnqueued('proj1', 'MNG-536', 'backlog-manager'); + // Cleared with the same workItemId it was marked with — normal cleanup path. + clearWorkItemEnqueued('proj1', 'MNG-536', 'backlog-manager'); + const result = await isWorkItemLocked('proj1', 'MNG-537', 'backlog-manager'); + expect(result.locked).toBe(false); + }); + + it('does NOT block other agent types on the same project', async () => { + markWorkItemEnqueued('proj1', 'MNG-536', 'backlog-manager'); + const result = await isWorkItemLocked('proj1', 'MNG-538', 'implementation'); + expect(result.locked).toBe(false); + }); + + it('DB query for backlog-manager omits workItemId so all project rows are counted', async () => { + vi.mocked(countActiveRuns).mockResolvedValueOnce(1); + const result = await isWorkItemLocked('proj1', 'MNG-537', 'backlog-manager'); + expect(result.locked).toBe(true); + const maxAgeMs = 2 * 30 * 60 * 1000; + expect(countActiveRuns).toHaveBeenCalledWith({ + projectId: 'proj1', + agentType: 'backlog-manager', + maxAgeMs, + }); + }); + + it('regular per-work-item agents still pass workItemId in the DB query', async () => { + vi.mocked(countActiveRuns).mockResolvedValueOnce(0); + await isWorkItemLocked('proj1', 'card1', 'implementation'); + const maxAgeMs = 2 * 30 * 60 * 1000; + expect(countActiveRuns).toHaveBeenCalledWith({ + projectId: 'proj1', + workItemId: 'card1', + agentType: 'implementation', + maxAgeMs, + }); + }); + }); }); From 0e18c50cfae378190f2929bf0c49ee0f514acc3a Mon Sep 17 00:00:00 2001 From: Zbigniew Sobiecki Date: Wed, 6 May 2026 11:44:47 +0000 Subject: [PATCH 2/2] chore(deps): npm audit fix to clear high-severity axios advisories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-existing dep drift on dev — `npm audit --omit=dev --audit-level=high` started failing CI when new axios advisories were published since the last dev push (2026-05-03). `npm audit fix` (no --force) bumps axios 1.15.0 → 1.16.0 inside the trello.js / jira.js subtrees via lockfile- only updates; no package.json changes, no breaking-change cascade. After: 5 moderate advisories remain (all transitive ip-address via @modelcontextprotocol/sdk → express-rate-limit) but the high-severity axios block is cleared, so the audit step exits 0. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 96 ++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index c953ab70a..726327567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,9 +98,9 @@ } }, "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.119.tgz", - "integrity": "sha512-6AvthpsaOTlkn514brSGOcCSLHDXODnU+ExN1O3CJCjxr5RBcmzR057C9EIM0G7IchnXsRfMZgRO1QKsjTXdbA==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.131.tgz", + "integrity": "sha512-4Xak+BlcxXuni5BvNeb0tnSapIoCBxE7cFnXvkUs0EwbY88FkmdJEtBXZbF7NRuN8bUwDeNxvy0Fs0dWnzpU+g==", "license": "SEE LICENSE IN README.md", "dependencies": { "@anthropic-ai/sdk": "^0.81.0", @@ -110,23 +110,23 @@ "node": ">=18.0.0" }, "optionalDependencies": { - "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.119", - "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.2.119", - "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.2.119", - "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.2.119", - "@anthropic-ai/claude-agent-sdk-linux-x64": "0.2.119", - "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.2.119", - "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.2.119", - "@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.119" + "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.131", + "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.2.131", + "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.2.131", + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.2.131", + "@anthropic-ai/claude-agent-sdk-linux-x64": "0.2.131", + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.2.131", + "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.2.131", + "@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.131" }, "peerDependencies": { "zod": "^4.0.0" } }, "node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.2.119.tgz", - "integrity": "sha512-kxnG37SZqUata2Jcp/YQ0n9Y7o/sinE/8LdG4ltM1gePh+z+0Mfa4vBUUTEBMBFth9PTovKoesIuVuyFpvO/Cw==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.2.131.tgz", + "integrity": "sha512-jOGq8lAi6bakqX0MBVkJDOddC2xSYnP1XHzps2cBF696dQlHoXs4hqU+69Wt4oKScyw4tM4Pe+Mmeut9LJqbEg==", "cpu": [ "arm64" ], @@ -137,9 +137,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.2.119.tgz", - "integrity": "sha512-9Aj8g3ELsmZuOFg17TCkikeg/Wt2ucVT8hOOPQUatzLd7BKhydrHLA0RP42nBpWECO1B/n/mPdQ4iS/LS3s2Fg==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.2.131.tgz", + "integrity": "sha512-IxewhApb20ucAxnpUCAwETLjO5PsQRAJIBBlDlNqPsd20LIZVVQuQ5orFf6CGEs6MfYRnWz2FYwfHhguGNPIyQ==", "cpu": [ "x64" ], @@ -150,12 +150,15 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.2.119.tgz", - "integrity": "sha512-v3o464XkiYehp/OKidQQirxdVb+aGSvdJvHF2zH9p33W8M/NC21zwwh4dhwDnKsyrtBIgkt2CcMwzIl30r0OtA==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.2.131.tgz", + "integrity": "sha512-GDwaga8aadtVeYq1wJM2BSWp5l/Srel7L5WRbEvkEWXeGP463S7VLJyiNVcbjbi/HLmyQigEkzFoHfZdeqKOvw==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -163,12 +166,15 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.2.119.tgz", - "integrity": "sha512-IPGWgtz+gGnD7fxKAvSf913EUT/lYBTBE8EZ7lh3+x5ZP2859LWLmrCm053Lf3nMWo/CWikZsVPwkDVwpz6tIQ==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.2.131.tgz", + "integrity": "sha512-7efL5otHqTKMeNxIztEjEGs8ktlR3hfMmVbo1HaEbs+tkJ6fvMwS3k4xnUP7Bqy+GsM+U9r9kRdNz4MVdc80hg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -176,12 +182,15 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.2.119.tgz", - "integrity": "sha512-9ePt4ZN+hsqDw4AgS4KtcWIGKfL9Oq28kwkrTER/QAcSrVKxiLonp81cCLzg7Ok/IUJu4Cfd71GZbFv/WE54zw==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.2.131.tgz", + "integrity": "sha512-tJJggvCGtkK876CowajF/42AdUy0TTJk0gHeCKuDCMJF3hMs70EtYnwyM81nb10tKUFb6zYdvn6iPn6iGx7iFQ==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -189,12 +198,15 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.2.119.tgz", - "integrity": "sha512-QYxFNAe4FFridPkKhGlNcNBJ0TaIygWYyvfI9g4kX0i+RVbresUWuZVkWY06ioJ0fXoixFJ+HNQBMB7dLrIp8Q==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.2.131.tgz", + "integrity": "sha512-WNqUJscB1F86Igbnw5zXpndT89I7l3aIvPJQEOrSA5JaIDmfJft8QA1rrJPwf2tcxP8nNS0H3MbEFBAxq92bNw==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -202,9 +214,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.2.119.tgz", - "integrity": "sha512-p/TjcKQvkCYtXGPlR+mdyNwqCmvRcQL34Wtq0yUZ+iqmI/eyCe59IJ3AZrE0EZoqmiAevEYzatPIt9sncC9uxw==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.2.131.tgz", + "integrity": "sha512-LDXYMqR3T1JtaIusmVDr6e539IhE+IULKYBiLC7+v7VvLG6niP1cC+4W/zYZRnUcbzUgcfoIi1FvrWhtF6/M+A==", "cpu": [ "arm64" ], @@ -215,9 +227,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": { - "version": "0.2.119", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.2.119.tgz", - "integrity": "sha512-k98Ju0wtktm6FhqTE/cXlVr6K4kGqBolVjEGzeKkW6ZILc7124euwNapAvkQCwMAavAxS/ZnO3jdKMtHtwTVTA==", + "version": "0.2.131", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.2.131.tgz", + "integrity": "sha512-gwLUkQWtK9Un2i9mWWQgoaEk+2rzamiH3r4j7aoTyVzB4ZQgxdBBOP9ac5o9pIwQE+vflr0HvKk1O54Z320Vng==", "cpu": [ "x64" ], @@ -4698,12 +4710,12 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } @@ -6876,9 +6888,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.0.tgz", + "integrity": "sha512-XKhFohWaSBdVJNTi5TaHziqnPkv04I9UQV6q1Wy7Ui6GGQZVW12ojDFwqer14EvCXxjvPG0CyWXx7cAXpALB4Q==", "license": "MIT", "dependencies": { "ip-address": "10.1.0"