diff --git a/.github/workflows/codegen-models.yml b/.github/workflows/codegen-models.yml index a77d0d008..ad2cbe46f 100644 --- a/.github/workflows/codegen-models.yml +++ b/.github/workflows/codegen-models.yml @@ -35,7 +35,7 @@ jobs: - name: Check for changes id: changes run: | - if git diff --quiet packages/sdk/src/models.generated.ts packages/sdk-py/agent_relay/models.py; then + if git diff --quiet packages/config/src/cli-registry.generated.ts packages/sdk-py/agent_relay/models.py packages/sdk-py/agent_relay/__init__.py; then echo "changed=false" >> $GITHUB_OUTPUT else echo "changed=true" >> $GITHUB_OUTPUT @@ -46,7 +46,7 @@ jobs: run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" - git add packages/sdk/src/models.generated.ts packages/sdk-py/agent_relay/models.py packages/sdk-py/agent_relay/__init__.py + git add packages/config/src/cli-registry.generated.ts packages/sdk-py/agent_relay/models.py packages/sdk-py/agent_relay/__init__.py git commit -m "chore: regenerate models from cli-registry.yaml [skip ci]" git push diff --git a/.trajectories/completed/2026-03/traj_42vchw32lfux.json b/.trajectories/completed/2026-03/traj_42vchw32lfux.json new file mode 100644 index 000000000..54a6631b2 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_42vchw32lfux.json @@ -0,0 +1,59 @@ +{ + "id": "traj_42vchw32lfux", + "version": 1, + "task": { + "title": "Fix install parity and spawn fallback" + }, + "status": "completed", + "startedAt": "2026-03-11T09:46:01.052Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-03-11T09:46:41.683Z" + } + ], + "chapters": [ + { + "id": "chap_aeox8kodppa4", + "title": "Work", + "agentName": "default", + "startedAt": "2026-03-11T09:46:41.683Z", + "events": [ + { + "ts": 1773222401684, + "type": "decision", + "content": "Changes were already complete and compiling - just needed commit and push: Changes were already complete and compiling - just needed commit and push", + "raw": { + "question": "Changes were already complete and compiling - just needed commit and push", + "chosen": "Changes were already complete and compiling - just needed commit and push", + "alternatives": [], + "reasoning": "Both postinstall.js and main.rs changes compiled cleanly with no issues found" + }, + "significance": "high" + } + ], + "endedAt": "2026-03-11T09:46:49.384Z" + } + ], + "commits": [ + "a672ab5b" + ], + "filesChanged": [ + "scripts/postinstall.js", + "src/main.rs" + ], + "projectId": "/Users/khaliqgant/Projects/relay", + "tags": [], + "_trace": { + "startRef": "af05fac6e893918b503b486d81145bfbc7ea3a7d", + "endRef": "a672ab5bd41f1337e5492611d843db2afabdc6bf", + "traceId": "trace_2wbe2z5krdcu" + }, + "completedAt": "2026-03-11T09:46:49.384Z", + "retrospective": { + "summary": "Committed and pushed install parity fixes (dashboard-server + relay-acp binary install) and spawn deserialization fallback. Spawned test-worker agent via relay.", + "approach": "Standard approach", + "confidence": 0.9 + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_42vchw32lfux.md b/.trajectories/completed/2026-03/traj_42vchw32lfux.md new file mode 100644 index 000000000..7b451c210 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_42vchw32lfux.md @@ -0,0 +1,38 @@ +# Trajectory: Fix install parity and spawn fallback + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** March 11, 2026 at 10:46 AM +> **Completed:** March 11, 2026 at 10:46 AM + +--- + +## Summary + +Committed and pushed install parity fixes (dashboard-server + relay-acp binary install) and spawn deserialization fallback. Spawned test-worker agent via relay. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Changes were already complete and compiling - just needed commit and push +- **Chose:** Changes were already complete and compiling - just needed commit and push +- **Reasoning:** Both postinstall.js and main.rs changes compiled cleanly with no issues found + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Changes were already complete and compiling - just needed commit and push: Changes were already complete and compiling - just needed commit and push + +--- + +## Artifacts + +**Commits:** a672ab5b +**Files changed:** 2 diff --git a/.trajectories/completed/2026-03/traj_42vchw32lfux.trace.json b/.trajectories/completed/2026-03/traj_42vchw32lfux.trace.json new file mode 100644 index 000000000..b5b9f8a63 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_42vchw32lfux.trace.json @@ -0,0 +1,69 @@ +{ + "version": 1, + "id": "trace_2wbe2z5krdcu", + "timestamp": "2026-03-11T09:46:49.448Z", + "trajectory": "traj_42vchw32lfux", + "files": [ + { + "path": "scripts/postinstall.js", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 373, + "end_line": 479, + "revision": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + }, + { + "start_line": 740, + "end_line": 759, + "revision": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + } + ] + } + ] + }, + { + "path": "src/main.rs", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 2366, + "end_line": 2379, + "revision": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + }, + { + "start_line": 2455, + "end_line": 2461, + "revision": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + }, + { + "start_line": 2465, + "end_line": 2471, + "revision": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + }, + { + "start_line": 2476, + "end_line": 2482, + "revision": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + }, + { + "start_line": 2592, + "end_line": 2736, + "revision": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_gvn35p8l5u4d.json b/.trajectories/completed/2026-03/traj_gvn35p8l5u4d.json new file mode 100644 index 000000000..9832119bd --- /dev/null +++ b/.trajectories/completed/2026-03/traj_gvn35p8l5u4d.json @@ -0,0 +1,25 @@ +{ + "id": "traj_gvn35p8l5u4d", + "version": 1, + "task": { + "title": "Investigate install path differences, Relaycast add_agent spawning, and dashboard DM visibility" + }, + "status": "completed", + "startedAt": "2026-03-11T09:24:36.060Z", + "agents": [], + "chapters": [], + "commits": [], + "filesChanged": [], + "projectId": "/Users/khaliqgant/Projects/relay", + "tags": [], + "_trace": { + "startRef": "af05fac6e893918b503b486d81145bfbc7ea3a7d", + "endRef": "af05fac6e893918b503b486d81145bfbc7ea3a7d" + }, + "completedAt": "2026-03-11T09:30:39.915Z", + "retrospective": { + "summary": "Patched relay-dashboard so Relaycast snapshots include message history, restoring agent-to-agent DM visibility in the dashboard. Added a regression test for /api/data and verified dashboard-server tests.", + "approach": "Standard approach", + "confidence": 0.92 + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_gvn35p8l5u4d.md b/.trajectories/completed/2026-03/traj_gvn35p8l5u4d.md new file mode 100644 index 000000000..52cb9dde3 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_gvn35p8l5u4d.md @@ -0,0 +1,14 @@ +# Trajectory: Investigate install path differences, Relaycast add_agent spawning, and dashboard DM visibility + +> **Status:** ✅ Completed +> **Confidence:** 92% +> **Started:** March 11, 2026 at 10:24 AM +> **Completed:** March 11, 2026 at 10:30 AM + +--- + +## Summary + +Patched relay-dashboard so Relaycast snapshots include message history, restoring agent-to-agent DM visibility in the dashboard. Added a regression test for /api/data and verified dashboard-server tests. + +**Approach:** Standard approach diff --git a/.trajectories/completed/2026-03/traj_lpyzzqwa2lwe.json b/.trajectories/completed/2026-03/traj_lpyzzqwa2lwe.json new file mode 100644 index 000000000..256bb65d0 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_lpyzzqwa2lwe.json @@ -0,0 +1,53 @@ +{ + "id": "traj_lpyzzqwa2lwe", + "version": 1, + "task": { + "title": "Investigate broker spawn flow for agent.spawn_requested silent drop" + }, + "status": "completed", + "startedAt": "2026-03-11T09:48:49.447Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-03-11T09:52:26.497Z" + } + ], + "chapters": [ + { + "id": "chap_efdzt8dmlag6", + "title": "Work", + "agentName": "default", + "startedAt": "2026-03-11T09:52:26.497Z", + "events": [ + { + "ts": 1773222746498, + "type": "decision", + "content": "Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key: Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key", + "raw": { + "question": "Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key", + "chosen": "Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key", + "alternatives": [], + "reasoning": "Preserves local WS echo suppression for event-id keyed spawns while preventing valid name-only broker spawn requests from being discarded before workers.spawn()" + }, + "significance": "high" + } + ], + "endedAt": "2026-03-11T09:53:31.244Z" + } + ], + "commits": [], + "filesChanged": [], + "projectId": "/Users/khaliqgant/Projects/relay", + "tags": [], + "_trace": { + "startRef": "a672ab5bd41f1337e5492611d843db2afabdc6bf", + "endRef": "a672ab5bd41f1337e5492611d843db2afabdc6bf" + }, + "completedAt": "2026-03-11T09:53:31.244Z", + "retrospective": { + "summary": "Fixed Relaycast broker spawn dedup so name-only agent.spawn_requested events reach workers.spawn while preserving local echo suppression for event-id keyed echoes; added regression tests", + "approach": "Standard approach", + "confidence": 0.88 + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_lpyzzqwa2lwe.md b/.trajectories/completed/2026-03/traj_lpyzzqwa2lwe.md new file mode 100644 index 000000000..8f490bcf6 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_lpyzzqwa2lwe.md @@ -0,0 +1,31 @@ +# Trajectory: Investigate broker spawn flow for agent.spawn_requested silent drop + +> **Status:** ✅ Completed +> **Confidence:** 88% +> **Started:** March 11, 2026 at 10:48 AM +> **Completed:** March 11, 2026 at 10:53 AM + +--- + +## Summary + +Fixed Relaycast broker spawn dedup so name-only agent.spawn_requested events reach workers.spawn while preserving local echo suppression for event-id keyed echoes; added regression tests + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key +- **Chose:** Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key +- **Reasoning:** Preserves local WS echo suppression for event-id keyed spawns while preventing valid name-only broker spawn requests from being discarded before workers.spawn() + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key: Condition branch-local spawn dedup on whether top-level control dedup already used the agent-name key diff --git a/.trajectories/completed/2026-03/traj_munpuaqqcqoa.json b/.trajectories/completed/2026-03/traj_munpuaqqcqoa.json new file mode 100644 index 000000000..50c4746ed --- /dev/null +++ b/.trajectories/completed/2026-03/traj_munpuaqqcqoa.json @@ -0,0 +1,74 @@ +{ + "id": "traj_munpuaqqcqoa", + "version": 1, + "task": { + "title": "Investigate DM leakage into relay-dashboard message feed" + }, + "status": "completed", + "startedAt": "2026-03-11T10:16:40.974Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-03-11T10:23:17.827Z" + } + ], + "chapters": [ + { + "id": "chap_lgd8zv7hd5vz", + "title": "Work", + "agentName": "default", + "startedAt": "2026-03-11T10:23:17.827Z", + "events": [ + { + "ts": 1773224597827, + "type": "decision", + "content": "Scoped the local agent feed to viewer-agent messages plus broadcasts: Scoped the local agent feed to viewer-agent messages plus broadcasts", + "raw": { + "question": "Scoped the local agent feed to viewer-agent messages plus broadcasts", + "chosen": "Scoped the local agent feed to viewer-agent messages plus broadcasts", + "alternatives": [], + "reasoning": "Relaycast snapshots intentionally merge channel history and DMs, but the local useMessages hook was rendering any message involving the selected agent, including third-party private DMs. Filtering in the client preserves history access while preventing feed leakage." + }, + "significance": "high" + } + ], + "endedAt": "2026-03-11T10:23:38.131Z" + } + ], + "commits": [ + "75aa63ac", + "54a556f8", + "dc846a21" + ], + "filesChanged": [ + ".trajectories/active/traj_munpuaqqcqoa.json", + ".trajectories/completed/2026-03/traj_42vchw32lfux.json", + ".trajectories/completed/2026-03/traj_42vchw32lfux.md", + ".trajectories/completed/2026-03/traj_42vchw32lfux.trace.json", + ".trajectories/completed/2026-03/traj_gvn35p8l5u4d.json", + ".trajectories/completed/2026-03/traj_gvn35p8l5u4d.md", + ".trajectories/completed/2026-03/traj_lpyzzqwa2lwe.json", + ".trajectories/completed/2026-03/traj_lpyzzqwa2lwe.md", + ".trajectories/completed/2026-03/traj_qy8m968k2ilc.json", + ".trajectories/completed/2026-03/traj_qy8m968k2ilc.md", + ".trajectories/completed/2026-03/traj_vvvmso7e0yw4.json", + ".trajectories/completed/2026-03/traj_vvvmso7e0yw4.md", + ".trajectories/index.json", + "packages/shared/cli-registry.yaml", + "scripts/postinstall.js" + ], + "projectId": "/Users/khaliqgant/Projects/relay", + "tags": [], + "_trace": { + "startRef": "43c701e14312e46fe11153b75990a45899d8129d", + "endRef": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e", + "traceId": "trace_e2ob1g09qbjk" + }, + "completedAt": "2026-03-11T10:23:38.131Z", + "retrospective": { + "summary": "Fixed relay-dashboard local feed leakage by filtering third-party private DMs out of selected-agent views and added regression tests", + "approach": "Standard approach", + "confidence": 0.93 + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_munpuaqqcqoa.md b/.trajectories/completed/2026-03/traj_munpuaqqcqoa.md new file mode 100644 index 000000000..ca41f01d3 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_munpuaqqcqoa.md @@ -0,0 +1,38 @@ +# Trajectory: Investigate DM leakage into relay-dashboard message feed + +> **Status:** ✅ Completed +> **Confidence:** 93% +> **Started:** March 11, 2026 at 11:16 AM +> **Completed:** March 11, 2026 at 11:23 AM + +--- + +## Summary + +Fixed relay-dashboard local feed leakage by filtering third-party private DMs out of selected-agent views and added regression tests + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Scoped the local agent feed to viewer-agent messages plus broadcasts +- **Chose:** Scoped the local agent feed to viewer-agent messages plus broadcasts +- **Reasoning:** Relaycast snapshots intentionally merge channel history and DMs, but the local useMessages hook was rendering any message involving the selected agent, including third-party private DMs. Filtering in the client preserves history access while preventing feed leakage. + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Scoped the local agent feed to viewer-agent messages plus broadcasts: Scoped the local agent feed to viewer-agent messages plus broadcasts + +--- + +## Artifacts + +**Commits:** 75aa63ac, 54a556f8, dc846a21 +**Files changed:** 15 diff --git a/.trajectories/completed/2026-03/traj_munpuaqqcqoa.trace.json b/.trajectories/completed/2026-03/traj_munpuaqqcqoa.trace.json new file mode 100644 index 000000000..355baede6 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_munpuaqqcqoa.trace.json @@ -0,0 +1,298 @@ +{ + "version": 1, + "id": "trace_e2ob1g09qbjk", + "timestamp": "2026-03-11T10:23:38.197Z", + "trajectory": "traj_munpuaqqcqoa", + "files": [ + { + "path": ".trajectories/active/traj_munpuaqqcqoa.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 19, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_42vchw32lfux.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 59, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_42vchw32lfux.md", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 38, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_42vchw32lfux.trace.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 69, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_gvn35p8l5u4d.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 25, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_gvn35p8l5u4d.md", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 14, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_lpyzzqwa2lwe.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 53, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_lpyzzqwa2lwe.md", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 31, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_qy8m968k2ilc.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 53, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_qy8m968k2ilc.md", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 31, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_vvvmso7e0yw4.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 25, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/completed/2026-03/traj_vvvmso7e0yw4.md", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 14, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": ".trajectories/index.json", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 1, + "end_line": 6, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + }, + { + "start_line": 807, + "end_line": 853, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": "packages/shared/cli-registry.yaml", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 12, + "end_line": 18, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + }, + { + "start_line": 30, + "end_line": 57, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + }, + { + "start_line": 60, + "end_line": 78, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + }, + { + "start_line": 83, + "end_line": 399, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + }, + { + "path": "scripts/postinstall.js", + "conversations": [ + { + "contributor": { + "type": "agent", + "model": "unknown" + }, + "ranges": [ + { + "start_line": 435, + "end_line": 449, + "revision": "75aa63ac1b0eabf698a3b9c3df8dfa8e4c2d937e" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_qy8m968k2ilc.json b/.trajectories/completed/2026-03/traj_qy8m968k2ilc.json new file mode 100644 index 000000000..fc04e4a98 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_qy8m968k2ilc.json @@ -0,0 +1,53 @@ +{ + "id": "traj_qy8m968k2ilc", + "version": 1, + "task": { + "title": "Relay worker session initialization" + }, + "status": "completed", + "startedAt": "2026-03-11T09:59:54.278Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-03-11T10:00:13.721Z" + } + ], + "chapters": [ + { + "id": "chap_oq7d1kr1afwq", + "title": "Work", + "agentName": "default", + "startedAt": "2026-03-11T10:00:13.721Z", + "events": [ + { + "ts": 1773223213722, + "type": "decision", + "content": "Used Lead as relay fallback contact because broker agent is absent from workspace: Used Lead as relay fallback contact because broker agent is absent from workspace", + "raw": { + "question": "Used Lead as relay fallback contact because broker agent is absent from workspace", + "chosen": "Used Lead as relay fallback contact because broker agent is absent from workspace", + "alternatives": [], + "reasoning": "DM to broker failed with agent not found; Lead is the active coordinating agent visible in Relaycast" + }, + "significance": "high" + } + ], + "endedAt": "2026-03-11T10:00:20.318Z" + } + ], + "commits": [], + "filesChanged": [], + "projectId": "/Users/khaliqgant/Projects/relay", + "tags": [], + "_trace": { + "startRef": "43c701e14312e46fe11153b75990a45899d8129d", + "endRef": "43c701e14312e46fe11153b75990a45899d8129d" + }, + "completedAt": "2026-03-11T10:00:20.318Z", + "retrospective": { + "summary": "Initialized relay worker session, verified workspace state, and notified Lead because broker was not present", + "approach": "Standard approach", + "confidence": 0.92 + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_qy8m968k2ilc.md b/.trajectories/completed/2026-03/traj_qy8m968k2ilc.md new file mode 100644 index 000000000..6bfbf6461 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_qy8m968k2ilc.md @@ -0,0 +1,31 @@ +# Trajectory: Relay worker session initialization + +> **Status:** ✅ Completed +> **Confidence:** 92% +> **Started:** March 11, 2026 at 10:59 AM +> **Completed:** March 11, 2026 at 11:00 AM + +--- + +## Summary + +Initialized relay worker session, verified workspace state, and notified Lead because broker was not present + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Used Lead as relay fallback contact because broker agent is absent from workspace +- **Chose:** Used Lead as relay fallback contact because broker agent is absent from workspace +- **Reasoning:** DM to broker failed with agent not found; Lead is the active coordinating agent visible in Relaycast + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Used Lead as relay fallback contact because broker agent is absent from workspace: Used Lead as relay fallback contact because broker agent is absent from workspace diff --git a/.trajectories/completed/2026-03/traj_vvvmso7e0yw4.json b/.trajectories/completed/2026-03/traj_vvvmso7e0yw4.json new file mode 100644 index 000000000..4e63f8882 --- /dev/null +++ b/.trajectories/completed/2026-03/traj_vvvmso7e0yw4.json @@ -0,0 +1,25 @@ +{ + "id": "traj_vvvmso7e0yw4", + "version": 1, + "task": { + "title": "Respond to relay connectivity messages" + }, + "status": "completed", + "startedAt": "2026-03-11T09:20:09.306Z", + "agents": [], + "chapters": [], + "commits": [], + "filesChanged": [], + "projectId": "/Users/khaliqgant/Projects/relay", + "tags": [], + "_trace": { + "startRef": "af05fac6e893918b503b486d81145bfbc7ea3a7d", + "endRef": "af05fac6e893918b503b486d81145bfbc7ea3a7d" + }, + "completedAt": "2026-03-11T09:20:12.356Z", + "retrospective": { + "summary": "Confirmed presence in Relaycast general channel, replied to Lead, and posted ACK when broker direct delivery was unavailable.", + "approach": "Standard approach", + "confidence": 0.96 + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-03/traj_vvvmso7e0yw4.md b/.trajectories/completed/2026-03/traj_vvvmso7e0yw4.md new file mode 100644 index 000000000..e93cf8a3b --- /dev/null +++ b/.trajectories/completed/2026-03/traj_vvvmso7e0yw4.md @@ -0,0 +1,14 @@ +# Trajectory: Respond to relay connectivity messages + +> **Status:** ✅ Completed +> **Confidence:** 96% +> **Started:** March 11, 2026 at 10:20 AM +> **Completed:** March 11, 2026 at 10:20 AM + +--- + +## Summary + +Confirmed presence in Relaycast general channel, replied to Lead, and posted ACK when broker direct delivery was unavailable. + +**Approach:** Standard approach diff --git a/.trajectories/index.json b/.trajectories/index.json index db8379151..8f8ed9d6d 100644 --- a/.trajectories/index.json +++ b/.trajectories/index.json @@ -1,6 +1,6 @@ { "version": 1, - "lastUpdated": "2026-03-11T08:59:31.669Z", + "lastUpdated": "2026-03-11T10:23:38.271Z", "trajectories": { "traj_1b1dj40sl6jl": { "title": "Revert aggressive retry logic in relay-pty-orchestrator", diff --git a/packages/config/src/cli-registry.generated.ts b/packages/config/src/cli-registry.generated.ts index ca2e44e03..8003982f0 100644 --- a/packages/config/src/cli-registry.generated.ts +++ b/packages/config/src/cli-registry.generated.ts @@ -12,14 +12,18 @@ * Update packages/shared/cli-registry.yaml to change versions. */ export const CLIVersions = { - /** Claude Code v2.1.50 */ - CLAUDE: '2.1.50', - /** Codex CLI v0.104.0 */ - CODEX: '0.104.0', - /** Gemini CLI v0.29.5 */ - GEMINI: '0.29.5', - /** Cursor v0.48.6 */ - CURSOR: '0.48.6', + /** Claude Code v2.1.72 */ + CLAUDE: '2.1.72', + /** Codex CLI v0.114.0 */ + CODEX: '0.114.0', + /** Gemini CLI v0.33.0 */ + GEMINI: '0.33.0', + /** Cursor v2026.02.27-e7d2ef6 */ + CURSOR: '2026.02.27-e7d2ef6', + /** Droid v0.1.0 */ + DROID: '0.1.0', + /** OpenCode v1.2.24 */ + OPENCODE: '1.2.24', /** Aider v0.72.1 */ AIDER: '0.72.1', /** Goose v1.0.16 */ @@ -34,6 +38,8 @@ export const CLIs = { CODEX: 'codex', GEMINI: 'gemini', CURSOR: 'cursor', + DROID: 'droid', + OPENCODE: 'opencode', AIDER: 'aider', GOOSE: 'goose', } as const; @@ -58,16 +64,18 @@ export type ClaudeModel = (typeof ClaudeModels)[keyof typeof ClaudeModels]; * Codex CLI model identifiers. */ export const CodexModels = { - /** GPT-5.2 Codex — Frontier agentic coding model (default) */ - GPT_5_2_CODEX: 'gpt-5.2-codex', - /** GPT-5.3 Codex — Latest frontier agentic coding model */ + /** GPT-5.4 — Latest frontier agentic coding model (default) */ + GPT_5_4: 'gpt-5.4', + /** GPT-5.3 Codex — Frontier agentic coding model */ GPT_5_3_CODEX: 'gpt-5.3-codex', /** GPT-5.3 Codex Spark — Ultra-fast coding model */ GPT_5_3_CODEX_SPARK: 'gpt-5.3-codex-spark', - /** GPT-5.1 Codex Max — Deep and fast reasoning */ - GPT_5_1_CODEX_MAX: 'gpt-5.1-codex-max', + /** GPT-5.2 Codex — Frontier agentic coding model */ + GPT_5_2_CODEX: 'gpt-5.2-codex', /** GPT-5.2 — Frontier model, knowledge & reasoning */ GPT_5_2: 'gpt-5.2', + /** GPT-5.1 Codex Max — Deep and fast reasoning */ + GPT_5_1_CODEX_MAX: 'gpt-5.1-codex-max', /** GPT-5.1 Codex Mini — Cheaper, faster */ GPT_5_1_CODEX_MINI: 'gpt-5.1-codex-mini', } as const; @@ -78,9 +86,11 @@ export type CodexModel = (typeof CodexModels)[keyof typeof CodexModels]; * Gemini CLI model identifiers. */ export const GeminiModels = { - /** Gemini 3 Pro Preview */ - GEMINI_3_PRO_PREVIEW: 'gemini-3-pro-preview', - /** Gemini 2.5 Pro (default) */ + /** Gemini 3.1 Pro Preview (default) */ + GEMINI_3_1_PRO_PREVIEW: 'gemini-3.1-pro-preview', + /** Gemini 3 Flash Preview */ + GEMINI_3_FLASH_PREVIEW: 'gemini-3-flash-preview', + /** Gemini 2.5 Pro */ GEMINI_2_5_PRO: 'gemini-2.5-pro', /** Gemini 2.5 Flash */ GEMINI_2_5_FLASH: 'gemini-2.5-flash', @@ -94,52 +104,220 @@ export type GeminiModel = (typeof GeminiModels)[keyof typeof GeminiModels]; * Cursor model identifiers. */ export const CursorModels = { - /** Claude 4.5 Opus (Thinking) (default) */ - OPUS_4_5_THINKING: 'opus-4.5-thinking', + /** Claude 4.6 Opus (Thinking) (default) */ + OPUS_4_6_THINKING: 'opus-4.6-thinking', + /** Claude 4.6 Opus */ + OPUS_4_6: 'opus-4.6', /** Claude 4.5 Opus */ OPUS_4_5: 'opus-4.5', + /** Claude 4.5 Opus (Thinking) */ + OPUS_4_5_THINKING: 'opus-4.5-thinking', + /** Claude 4.6 Sonnet */ + SONNET_4_6: 'sonnet-4.6', + /** Claude 4.6 Sonnet (Thinking) */ + SONNET_4_6_THINKING: 'sonnet-4.6-thinking', /** Claude 4.5 Sonnet */ SONNET_4_5: 'sonnet-4.5', /** Claude 4.5 Sonnet (Thinking) */ SONNET_4_5_THINKING: 'sonnet-4.5-thinking', - /** GPT-5.2 Codex */ - GPT_5_2_CODEX: 'gpt-5.2-codex', - /** GPT-5.2 Codex High */ - GPT_5_2_CODEX_HIGH: 'gpt-5.2-codex-high', - /** GPT-5.2 Codex Low */ - GPT_5_2_CODEX_LOW: 'gpt-5.2-codex-low', + /** Composer 1.5 */ + COMPOSER_1_5: 'composer-1.5', + /** Composer 1 */ + COMPOSER_1: 'composer-1', + /** GPT-5.4 Extra High */ + GPT_5_4_XHIGH: 'gpt-5.4-xhigh', + /** GPT-5.4 Extra High Fast */ + GPT_5_4_XHIGH_FAST: 'gpt-5.4-xhigh-fast', + /** GPT-5.4 High */ + GPT_5_4_HIGH: 'gpt-5.4-high', + /** GPT-5.4 High Fast */ + GPT_5_4_HIGH_FAST: 'gpt-5.4-high-fast', + /** GPT-5.4 */ + GPT_5_4_MEDIUM: 'gpt-5.4-medium', + /** GPT-5.4 Fast */ + GPT_5_4_MEDIUM_FAST: 'gpt-5.4-medium-fast', + /** GPT-5.4 Low */ + GPT_5_4_LOW: 'gpt-5.4-low', + /** GPT-5.3 Codex Extra High */ + GPT_5_3_CODEX_XHIGH: 'gpt-5.3-codex-xhigh', + /** GPT-5.3 Codex Extra High Fast */ + GPT_5_3_CODEX_XHIGH_FAST: 'gpt-5.3-codex-xhigh-fast', + /** GPT-5.3 Codex High */ + GPT_5_3_CODEX_HIGH: 'gpt-5.3-codex-high', + /** GPT-5.3 Codex High Fast */ + GPT_5_3_CODEX_HIGH_FAST: 'gpt-5.3-codex-high-fast', + /** GPT-5.3 Codex */ + GPT_5_3_CODEX: 'gpt-5.3-codex', + /** GPT-5.3 Codex Fast */ + GPT_5_3_CODEX_FAST: 'gpt-5.3-codex-fast', + /** GPT-5.3 Codex Low */ + GPT_5_3_CODEX_LOW: 'gpt-5.3-codex-low', + /** GPT-5.3 Codex Low Fast */ + GPT_5_3_CODEX_LOW_FAST: 'gpt-5.3-codex-low-fast', + /** GPT-5.3 Codex Spark */ + GPT_5_3_CODEX_SPARK_PREVIEW: 'gpt-5.3-codex-spark-preview', /** GPT-5.2 Codex Extra High */ GPT_5_2_CODEX_XHIGH: 'gpt-5.2-codex-xhigh', - /** GPT-5.2 Codex Fast */ - GPT_5_2_CODEX_FAST: 'gpt-5.2-codex-fast', + /** GPT-5.2 Codex Extra High Fast */ + GPT_5_2_CODEX_XHIGH_FAST: 'gpt-5.2-codex-xhigh-fast', + /** GPT-5.2 Codex High */ + GPT_5_2_CODEX_HIGH: 'gpt-5.2-codex-high', /** GPT-5.2 Codex High Fast */ GPT_5_2_CODEX_HIGH_FAST: 'gpt-5.2-codex-high-fast', + /** GPT-5.2 Codex */ + GPT_5_2_CODEX: 'gpt-5.2-codex', + /** GPT-5.2 Codex Fast */ + GPT_5_2_CODEX_FAST: 'gpt-5.2-codex-fast', + /** GPT-5.2 Codex Low */ + GPT_5_2_CODEX_LOW: 'gpt-5.2-codex-low', /** GPT-5.2 Codex Low Fast */ GPT_5_2_CODEX_LOW_FAST: 'gpt-5.2-codex-low-fast', - /** GPT-5.2 Codex Extra High Fast */ - GPT_5_2_CODEX_XHIGH_FAST: 'gpt-5.2-codex-xhigh-fast', - /** GPT-5.1 Codex Max */ - GPT_5_1_CODEX_MAX: 'gpt-5.1-codex-max', - /** GPT-5.1 Codex Max High */ - GPT_5_1_CODEX_MAX_HIGH: 'gpt-5.1-codex-max-high', /** GPT-5.2 */ GPT_5_2: 'gpt-5.2', /** GPT-5.2 High */ GPT_5_2_HIGH: 'gpt-5.2-high', + /** GPT-5.1 Codex Max */ + GPT_5_1_CODEX_MAX: 'gpt-5.1-codex-max', + /** GPT-5.1 Codex Max High */ + GPT_5_1_CODEX_MAX_HIGH: 'gpt-5.1-codex-max-high', + /** GPT-5.1 Codex Mini */ + GPT_5_1_CODEX_MINI: 'gpt-5.1-codex-mini', /** GPT-5.1 High */ GPT_5_1_HIGH: 'gpt-5.1-high', + /** Gemini 3.1 Pro */ + GEMINI_3_1_PRO: 'gemini-3.1-pro', /** Gemini 3 Pro */ GEMINI_3_PRO: 'gemini-3-pro', /** Gemini 3 Flash */ GEMINI_3_FLASH: 'gemini-3-flash', - /** Composer 1 */ - COMPOSER_1: 'composer-1', /** Grok */ GROK: 'grok', + /** Kimi K2.5 */ + KIMI_K2_5: 'kimi-k2.5', } as const; export type CursorModel = (typeof CursorModels)[keyof typeof CursorModels]; +/** + * Droid model identifiers. + */ +export const DroidModels = { + /** Opus 4.6 Fast Mode (12x) (default) */ + OPUS_4_6_FAST: 'opus-4.6-fast', + /** Opus 4.5 (2x) */ + OPUS_4_5: 'opus-4.5', + /** Sonnet 4.5 (1.2x) */ + SONNET_4_5: 'sonnet-4.5', + /** Haiku 4.5 (0.4x) */ + HAIKU_4_5: 'haiku-4.5', + /** GPT-5.2 (0.7x) */ + GPT_5_2: 'gpt-5.2', + /** GPT-5.2 Codex (0.7x) */ + GPT_5_2_CODEX: 'gpt-5.2-codex', + /** Gemini 3 Flash (0.2x) */ + GEMINI_3_FLASH: 'gemini-3-flash', + /** Droid Core (GLM-4.7) (0.25x) */ + DROID_CORE: 'droid-core-glm-4.7', +} as const; + +export type DroidModel = (typeof DroidModels)[keyof typeof DroidModels]; + +/** + * OpenCode model identifiers. + */ +export const OpencodeModels = { + /** Big Pickle */ + OPENCODE_BIG_PICKLE: 'opencode/big-pickle', + /** GPT-5 Nano (OpenCode) */ + OPENCODE_GPT_5_NANO: 'opencode/gpt-5-nano', + /** Mimo V2 Flash Free */ + OPENCODE_MIMO_V2_FLASH_FREE: 'opencode/mimo-v2-flash-free', + /** MiniMax M2.5 Free */ + OPENCODE_MINIMAX_M2_5_FREE: 'opencode/minimax-m2.5-free', + /** Codex Mini Latest */ + OPENAI_CODEX_MINI_LATEST: 'openai/codex-mini-latest', + /** GPT-3.5 Turbo */ + OPENAI_GPT_3_5_TURBO: 'openai/gpt-3.5-turbo', + /** GPT-4 */ + OPENAI_GPT_4: 'openai/gpt-4', + /** GPT-4 Turbo */ + OPENAI_GPT_4_TURBO: 'openai/gpt-4-turbo', + /** GPT-4.1 */ + OPENAI_GPT_4_1: 'openai/gpt-4.1', + /** GPT-4.1 Mini */ + OPENAI_GPT_4_1_MINI: 'openai/gpt-4.1-mini', + /** GPT-4.1 Nano */ + OPENAI_GPT_4_1_NANO: 'openai/gpt-4.1-nano', + /** GPT-4o */ + OPENAI_GPT_4O: 'openai/gpt-4o', + /** GPT-4o (2024-05-13) */ + OPENAI_GPT_4O_2024_05_13: 'openai/gpt-4o-2024-05-13', + /** GPT-4o (2024-08-06) */ + OPENAI_GPT_4O_2024_08_06: 'openai/gpt-4o-2024-08-06', + /** GPT-4o (2024-11-20) */ + OPENAI_GPT_4O_2024_11_20: 'openai/gpt-4o-2024-11-20', + /** GPT-4o Mini */ + OPENAI_GPT_4O_MINI: 'openai/gpt-4o-mini', + /** GPT-5 */ + OPENAI_GPT_5: 'openai/gpt-5', + /** GPT-5 Codex */ + OPENAI_GPT_5_CODEX: 'openai/gpt-5-codex', + /** GPT-5 Mini */ + OPENAI_GPT_5_MINI: 'openai/gpt-5-mini', + /** GPT-5 Nano */ + OPENAI_GPT_5_NANO: 'openai/gpt-5-nano', + /** GPT-5 Pro */ + OPENAI_GPT_5_PRO: 'openai/gpt-5-pro', + /** GPT-5.1 */ + OPENAI_GPT_5_1: 'openai/gpt-5.1', + /** GPT-5.1 Chat Latest */ + OPENAI_GPT_5_1_CHAT_LATEST: 'openai/gpt-5.1-chat-latest', + /** GPT-5.1 Codex */ + OPENAI_GPT_5_1_CODEX: 'openai/gpt-5.1-codex', + /** GPT-5.1 Codex Max */ + OPENAI_GPT_5_1_CODEX_MAX: 'openai/gpt-5.1-codex-max', + /** GPT-5.1 Codex Mini */ + OPENAI_GPT_5_1_CODEX_MINI: 'openai/gpt-5.1-codex-mini', + /** GPT-5.2 (default) */ + OPENAI_GPT_5_2: 'openai/gpt-5.2', + /** GPT-5.2 Chat Latest */ + OPENAI_GPT_5_2_CHAT_LATEST: 'openai/gpt-5.2-chat-latest', + /** GPT-5.2 Codex */ + OPENAI_GPT_5_2_CODEX: 'openai/gpt-5.2-codex', + /** GPT-5.2 Pro */ + OPENAI_GPT_5_2_PRO: 'openai/gpt-5.2-pro', + /** GPT-5.3 Codex */ + OPENAI_GPT_5_3_CODEX: 'openai/gpt-5.3-codex', + /** GPT-5.3 Codex Spark */ + OPENAI_GPT_5_3_CODEX_SPARK: 'openai/gpt-5.3-codex-spark', + /** GPT-5.4 */ + OPENAI_GPT_5_4: 'openai/gpt-5.4', + /** GPT-5.4 Pro */ + OPENAI_GPT_5_4_PRO: 'openai/gpt-5.4-pro', + /** O1 */ + OPENAI_O1: 'openai/o1', + /** O1 Mini */ + OPENAI_O1_MINI: 'openai/o1-mini', + /** O1 Preview */ + OPENAI_O1_PREVIEW: 'openai/o1-preview', + /** O1 Pro */ + OPENAI_O1_PRO: 'openai/o1-pro', + /** O3 */ + OPENAI_O3: 'openai/o3', + /** O3 Deep Research */ + OPENAI_O3_DEEP_RESEARCH: 'openai/o3-deep-research', + /** O3 Mini */ + OPENAI_O3_MINI: 'openai/o3-mini', + /** O3 Pro */ + OPENAI_O3_PRO: 'openai/o3-pro', + /** O4 Mini */ + OPENAI_O4_MINI: 'openai/o4-mini', + /** O4 Mini Deep Research */ + OPENAI_O4_MINI_DEEP_RESEARCH: 'openai/o4-mini-deep-research', +} as const; + +export type OpencodeModel = (typeof OpencodeModels)[keyof typeof OpencodeModels]; + /** Model option type for UI dropdowns */ export interface ModelOption { value: string; @@ -159,11 +337,12 @@ export const CLAUDE_MODEL_OPTIONS: ModelOption[] = [ * Codex CLI model options for UI dropdowns. */ export const CODEX_MODEL_OPTIONS: ModelOption[] = [ - { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex — Frontier agentic coding model' }, - { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex — Latest frontier agentic coding model' }, + { value: 'gpt-5.4', label: 'GPT-5.4 — Latest frontier agentic coding model' }, + { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex — Frontier agentic coding model' }, { value: 'gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark — Ultra-fast coding model' }, - { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max — Deep and fast reasoning' }, + { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex — Frontier agentic coding model' }, { value: 'gpt-5.2', label: 'GPT-5.2 — Frontier model, knowledge & reasoning' }, + { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max — Deep and fast reasoning' }, { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini — Cheaper, faster' }, ]; @@ -171,7 +350,8 @@ export const CODEX_MODEL_OPTIONS: ModelOption[] = [ * Gemini CLI model options for UI dropdowns. */ export const GEMINI_MODEL_OPTIONS: ModelOption[] = [ - { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' }, + { value: 'gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro Preview' }, + { value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' }, { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' }, { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' }, { value: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite' }, @@ -181,27 +361,115 @@ export const GEMINI_MODEL_OPTIONS: ModelOption[] = [ * Cursor model options for UI dropdowns. */ export const CURSOR_MODEL_OPTIONS: ModelOption[] = [ - { value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' }, + { value: 'opus-4.6-thinking', label: 'Claude 4.6 Opus (Thinking)' }, + { value: 'opus-4.6', label: 'Claude 4.6 Opus' }, { value: 'opus-4.5', label: 'Claude 4.5 Opus' }, + { value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' }, + { value: 'sonnet-4.6', label: 'Claude 4.6 Sonnet' }, + { value: 'sonnet-4.6-thinking', label: 'Claude 4.6 Sonnet (Thinking)' }, { value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' }, { value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' }, - { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' }, - { value: 'gpt-5.2-codex-high', label: 'GPT-5.2 Codex High' }, - { value: 'gpt-5.2-codex-low', label: 'GPT-5.2 Codex Low' }, + { value: 'composer-1.5', label: 'Composer 1.5' }, + { value: 'composer-1', label: 'Composer 1' }, + { value: 'gpt-5.4-xhigh', label: 'GPT-5.4 Extra High' }, + { value: 'gpt-5.4-xhigh-fast', label: 'GPT-5.4 Extra High Fast' }, + { value: 'gpt-5.4-high', label: 'GPT-5.4 High' }, + { value: 'gpt-5.4-high-fast', label: 'GPT-5.4 High Fast' }, + { value: 'gpt-5.4-medium', label: 'GPT-5.4' }, + { value: 'gpt-5.4-medium-fast', label: 'GPT-5.4 Fast' }, + { value: 'gpt-5.4-low', label: 'GPT-5.4 Low' }, + { value: 'gpt-5.3-codex-xhigh', label: 'GPT-5.3 Codex Extra High' }, + { value: 'gpt-5.3-codex-xhigh-fast', label: 'GPT-5.3 Codex Extra High Fast' }, + { value: 'gpt-5.3-codex-high', label: 'GPT-5.3 Codex High' }, + { value: 'gpt-5.3-codex-high-fast', label: 'GPT-5.3 Codex High Fast' }, + { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' }, + { value: 'gpt-5.3-codex-fast', label: 'GPT-5.3 Codex Fast' }, + { value: 'gpt-5.3-codex-low', label: 'GPT-5.3 Codex Low' }, + { value: 'gpt-5.3-codex-low-fast', label: 'GPT-5.3 Codex Low Fast' }, + { value: 'gpt-5.3-codex-spark-preview', label: 'GPT-5.3 Codex Spark' }, { value: 'gpt-5.2-codex-xhigh', label: 'GPT-5.2 Codex Extra High' }, - { value: 'gpt-5.2-codex-fast', label: 'GPT-5.2 Codex Fast' }, + { value: 'gpt-5.2-codex-xhigh-fast', label: 'GPT-5.2 Codex Extra High Fast' }, + { value: 'gpt-5.2-codex-high', label: 'GPT-5.2 Codex High' }, { value: 'gpt-5.2-codex-high-fast', label: 'GPT-5.2 Codex High Fast' }, + { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' }, + { value: 'gpt-5.2-codex-fast', label: 'GPT-5.2 Codex Fast' }, + { value: 'gpt-5.2-codex-low', label: 'GPT-5.2 Codex Low' }, { value: 'gpt-5.2-codex-low-fast', label: 'GPT-5.2 Codex Low Fast' }, - { value: 'gpt-5.2-codex-xhigh-fast', label: 'GPT-5.2 Codex Extra High Fast' }, - { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' }, - { value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' }, { value: 'gpt-5.2', label: 'GPT-5.2' }, { value: 'gpt-5.2-high', label: 'GPT-5.2 High' }, + { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' }, + { value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' }, + { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' }, { value: 'gpt-5.1-high', label: 'GPT-5.1 High' }, + { value: 'gemini-3.1-pro', label: 'Gemini 3.1 Pro' }, { value: 'gemini-3-pro', label: 'Gemini 3 Pro' }, { value: 'gemini-3-flash', label: 'Gemini 3 Flash' }, - { value: 'composer-1', label: 'Composer 1' }, { value: 'grok', label: 'Grok' }, + { value: 'kimi-k2.5', label: 'Kimi K2.5' }, +]; + +/** + * Droid model options for UI dropdowns. + */ +export const DROID_MODEL_OPTIONS: ModelOption[] = [ + { value: 'opus-4.6-fast', label: 'Opus 4.6 Fast Mode (12x)' }, + { value: 'opus-4.5', label: 'Opus 4.5 (2x)' }, + { value: 'sonnet-4.5', label: 'Sonnet 4.5 (1.2x)' }, + { value: 'haiku-4.5', label: 'Haiku 4.5 (0.4x)' }, + { value: 'gpt-5.2', label: 'GPT-5.2 (0.7x)' }, + { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex (0.7x)' }, + { value: 'gemini-3-flash', label: 'Gemini 3 Flash (0.2x)' }, + { value: 'droid-core-glm-4.7', label: 'Droid Core (GLM-4.7) (0.25x)' }, +]; + +/** + * OpenCode model options for UI dropdowns. + */ +export const OPENCODE_MODEL_OPTIONS: ModelOption[] = [ + { value: 'opencode/big-pickle', label: 'Big Pickle' }, + { value: 'opencode/gpt-5-nano', label: 'GPT-5 Nano (OpenCode)' }, + { value: 'opencode/mimo-v2-flash-free', label: 'Mimo V2 Flash Free' }, + { value: 'opencode/minimax-m2.5-free', label: 'MiniMax M2.5 Free' }, + { value: 'openai/codex-mini-latest', label: 'Codex Mini Latest' }, + { value: 'openai/gpt-3.5-turbo', label: 'GPT-3.5 Turbo' }, + { value: 'openai/gpt-4', label: 'GPT-4' }, + { value: 'openai/gpt-4-turbo', label: 'GPT-4 Turbo' }, + { value: 'openai/gpt-4.1', label: 'GPT-4.1' }, + { value: 'openai/gpt-4.1-mini', label: 'GPT-4.1 Mini' }, + { value: 'openai/gpt-4.1-nano', label: 'GPT-4.1 Nano' }, + { value: 'openai/gpt-4o', label: 'GPT-4o' }, + { value: 'openai/gpt-4o-2024-05-13', label: 'GPT-4o (2024-05-13)' }, + { value: 'openai/gpt-4o-2024-08-06', label: 'GPT-4o (2024-08-06)' }, + { value: 'openai/gpt-4o-2024-11-20', label: 'GPT-4o (2024-11-20)' }, + { value: 'openai/gpt-4o-mini', label: 'GPT-4o Mini' }, + { value: 'openai/gpt-5', label: 'GPT-5' }, + { value: 'openai/gpt-5-codex', label: 'GPT-5 Codex' }, + { value: 'openai/gpt-5-mini', label: 'GPT-5 Mini' }, + { value: 'openai/gpt-5-nano', label: 'GPT-5 Nano' }, + { value: 'openai/gpt-5-pro', label: 'GPT-5 Pro' }, + { value: 'openai/gpt-5.1', label: 'GPT-5.1' }, + { value: 'openai/gpt-5.1-chat-latest', label: 'GPT-5.1 Chat Latest' }, + { value: 'openai/gpt-5.1-codex', label: 'GPT-5.1 Codex' }, + { value: 'openai/gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' }, + { value: 'openai/gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' }, + { value: 'openai/gpt-5.2', label: 'GPT-5.2' }, + { value: 'openai/gpt-5.2-chat-latest', label: 'GPT-5.2 Chat Latest' }, + { value: 'openai/gpt-5.2-codex', label: 'GPT-5.2 Codex' }, + { value: 'openai/gpt-5.2-pro', label: 'GPT-5.2 Pro' }, + { value: 'openai/gpt-5.3-codex', label: 'GPT-5.3 Codex' }, + { value: 'openai/gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark' }, + { value: 'openai/gpt-5.4', label: 'GPT-5.4' }, + { value: 'openai/gpt-5.4-pro', label: 'GPT-5.4 Pro' }, + { value: 'openai/o1', label: 'O1' }, + { value: 'openai/o1-mini', label: 'O1 Mini' }, + { value: 'openai/o1-preview', label: 'O1 Preview' }, + { value: 'openai/o1-pro', label: 'O1 Pro' }, + { value: 'openai/o3', label: 'O3' }, + { value: 'openai/o3-deep-research', label: 'O3 Deep Research' }, + { value: 'openai/o3-mini', label: 'O3 Mini' }, + { value: 'openai/o3-pro', label: 'O3 Pro' }, + { value: 'openai/o4-mini', label: 'O4 Mini' }, + { value: 'openai/o4-mini-deep-research', label: 'O4 Mini Deep Research' }, ]; /** @@ -220,6 +488,8 @@ export const Models = { Codex: CodexModels, Gemini: GeminiModels, Cursor: CursorModels, + Droid: DroidModels, + Opencode: OpencodeModels, } as const; /** @@ -239,6 +509,8 @@ export const ModelOptions = { Codex: CODEX_MODEL_OPTIONS, Gemini: GEMINI_MODEL_OPTIONS, Cursor: CURSOR_MODEL_OPTIONS, + Droid: DROID_MODEL_OPTIONS, + Opencode: OPENCODE_MODEL_OPTIONS, } as const; /** @@ -276,31 +548,45 @@ export const CLIRegistry = { claude: { name: 'Claude Code', package: '@anthropic-ai/claude-code', - version: '2.1.50', + version: '2.1.72', install: 'npm install -g @anthropic-ai/claude-code', npmLink: 'https://www.npmjs.com/package/@anthropic-ai', }, codex: { name: 'Codex CLI', package: '@openai/codex', - version: '0.104.0', + version: '0.114.0', install: 'npm install -g @openai/codex', npmLink: 'https://www.npmjs.com/package/@openai/codex', }, gemini: { name: 'Gemini CLI', package: '@google/gemini-cli', - version: '0.29.5', + version: '0.33.0', install: 'npm install -g @google/gemini-cli', npmLink: 'https://www.npmjs.com/package/@google/gemini-cli', }, cursor: { name: 'Cursor', package: 'cursor', - version: '0.48.6', + version: '2026.02.27-e7d2ef6', install: 'Download from cursor.com', npmLink: undefined, }, + droid: { + name: 'Droid', + package: 'droid', + version: '0.1.0', + install: 'Download from droid.dev', + npmLink: undefined, + }, + opencode: { + name: 'OpenCode', + package: 'opencode-ai', + version: '1.2.24', + install: 'npm install -g opencode-ai', + npmLink: 'https://www.npmjs.com/package/opencode-ai', + }, aider: { name: 'Aider', package: 'aider-chat', @@ -322,7 +608,9 @@ export const CLIRegistry = { */ export const DefaultModels = { claude: 'sonnet', - codex: 'gpt-5.2-codex', - gemini: 'gemini-2.5-pro', - cursor: 'opus-4.5-thinking', + codex: 'gpt-5.4', + gemini: 'gemini-3.1-pro-preview', + cursor: 'opus-4.6-thinking', + droid: 'opus-4.6-fast', + opencode: 'openai/gpt-5.2', } as const; diff --git a/packages/sdk-py/agent_relay/__init__.py b/packages/sdk-py/agent_relay/__init__.py index ce6344f9f..5f6b09e22 100644 --- a/packages/sdk-py/agent_relay/__init__.py +++ b/packages/sdk-py/agent_relay/__init__.py @@ -1,18 +1,21 @@ -"""Agent Relay Python SDK — re-exports from src/agent_relay/. +"""Agent Relay Python SDK.""" -This directory exists for backward compatibility with codegen scripts. -The real package source lives in src/agent_relay/. -""" +from .models import ( + CLIs, + CLIVersions, + CLI_REGISTRY, + DEFAULT_MODELS, + Models, + ModelOptions, + SwarmPatterns, +) -import importlib.util as _util -import sys as _sys -from pathlib import Path as _Path - -# Load the real package from src/agent_relay/ and replace this module -_src_init = _Path(__file__).resolve().parent.parent / "src" / "agent_relay" / "__init__.py" -_spec = _util.spec_from_file_location("agent_relay", str(_src_init), - submodule_search_locations=[str(_src_init.parent)]) -assert _spec is not None and _spec.loader is not None, f"Could not load {_src_init}" -_real = _util.module_from_spec(_spec) -_sys.modules[__name__] = _real -_spec.loader.exec_module(_real) +__all__ = [ + "CLIs", + "CLIVersions", + "CLI_REGISTRY", + "DEFAULT_MODELS", + "Models", + "ModelOptions", + "SwarmPatterns", +] diff --git a/packages/sdk-py/agent_relay/models.py b/packages/sdk-py/agent_relay/models.py index 31835fb6d..8bd8ec604 100644 --- a/packages/sdk-py/agent_relay/models.py +++ b/packages/sdk-py/agent_relay/models.py @@ -9,10 +9,12 @@ class CLIVersions: """CLI tool versions. Update packages/shared/cli-registry.yaml to change versions.""" - CLAUDE: Final[str] = "2.1.50" # Claude Code - CODEX: Final[str] = "0.104.0" # Codex CLI - GEMINI: Final[str] = "0.29.5" # Gemini CLI - CURSOR: Final[str] = "0.48.6" # Cursor + CLAUDE: Final[str] = "2.1.72" # Claude Code + CODEX: Final[str] = "0.114.0" # Codex CLI + GEMINI: Final[str] = "0.33.0" # Gemini CLI + CURSOR: Final[str] = "2026.02.27-e7d2ef6" # Cursor + DROID: Final[str] = "0.1.0" # Droid + OPENCODE: Final[str] = "1.2.24" # OpenCode AIDER: Final[str] = "0.72.1" # Aider GOOSE: Final[str] = "1.0.16" # Goose @@ -23,6 +25,8 @@ class CLIs: CODEX: Final[str] = "codex" GEMINI: Final[str] = "gemini" CURSOR: Final[str] = "cursor" + DROID: Final[str] = "droid" + OPENCODE: Final[str] = "opencode" AIDER: Final[str] = "aider" GOOSE: Final[str] = "goose" @@ -36,45 +40,131 @@ class ClaudeModels: class CodexModels: """Codex CLI model identifiers.""" - GPT_5_2_CODEX: Final[str] = "gpt-5.2-codex" # GPT-5.2 Codex — Frontier agentic coding model (default) - GPT_5_3_CODEX: Final[str] = "gpt-5.3-codex" # GPT-5.3 Codex — Latest frontier agentic coding model + GPT_5_4: Final[str] = "gpt-5.4" # GPT-5.4 — Latest frontier agentic coding model (default) + GPT_5_3_CODEX: Final[str] = "gpt-5.3-codex" # GPT-5.3 Codex — Frontier agentic coding model GPT_5_3_CODEX_SPARK: Final[str] = "gpt-5.3-codex-spark" # GPT-5.3 Codex Spark — Ultra-fast coding model - GPT_5_1_CODEX_MAX: Final[str] = "gpt-5.1-codex-max" # GPT-5.1 Codex Max — Deep and fast reasoning + GPT_5_2_CODEX: Final[str] = "gpt-5.2-codex" # GPT-5.2 Codex — Frontier agentic coding model GPT_5_2: Final[str] = "gpt-5.2" # GPT-5.2 — Frontier model, knowledge & reasoning + GPT_5_1_CODEX_MAX: Final[str] = "gpt-5.1-codex-max" # GPT-5.1 Codex Max — Deep and fast reasoning GPT_5_1_CODEX_MINI: Final[str] = "gpt-5.1-codex-mini" # GPT-5.1 Codex Mini — Cheaper, faster class GeminiModels: """Gemini CLI model identifiers.""" - GEMINI_3_PRO_PREVIEW: Final[str] = "gemini-3-pro-preview" # Gemini 3 Pro Preview - GEMINI_2_5_PRO: Final[str] = "gemini-2.5-pro" # Gemini 2.5 Pro (default) + GEMINI_3_1_PRO_PREVIEW: Final[str] = "gemini-3.1-pro-preview" # Gemini 3.1 Pro Preview (default) + GEMINI_3_FLASH_PREVIEW: Final[str] = "gemini-3-flash-preview" # Gemini 3 Flash Preview + GEMINI_2_5_PRO: Final[str] = "gemini-2.5-pro" # Gemini 2.5 Pro GEMINI_2_5_FLASH: Final[str] = "gemini-2.5-flash" # Gemini 2.5 Flash GEMINI_2_5_FLASH_LITE: Final[str] = "gemini-2.5-flash-lite" # Gemini 2.5 Flash Lite class CursorModels: """Cursor model identifiers.""" - OPUS_4_5_THINKING: Final[str] = "opus-4.5-thinking" # Claude 4.5 Opus (Thinking) (default) + OPUS_4_6_THINKING: Final[str] = "opus-4.6-thinking" # Claude 4.6 Opus (Thinking) (default) + OPUS_4_6: Final[str] = "opus-4.6" # Claude 4.6 Opus OPUS_4_5: Final[str] = "opus-4.5" # Claude 4.5 Opus + OPUS_4_5_THINKING: Final[str] = "opus-4.5-thinking" # Claude 4.5 Opus (Thinking) + SONNET_4_6: Final[str] = "sonnet-4.6" # Claude 4.6 Sonnet + SONNET_4_6_THINKING: Final[str] = "sonnet-4.6-thinking" # Claude 4.6 Sonnet (Thinking) SONNET_4_5: Final[str] = "sonnet-4.5" # Claude 4.5 Sonnet SONNET_4_5_THINKING: Final[str] = "sonnet-4.5-thinking" # Claude 4.5 Sonnet (Thinking) - GPT_5_2_CODEX: Final[str] = "gpt-5.2-codex" # GPT-5.2 Codex - GPT_5_2_CODEX_HIGH: Final[str] = "gpt-5.2-codex-high" # GPT-5.2 Codex High - GPT_5_2_CODEX_LOW: Final[str] = "gpt-5.2-codex-low" # GPT-5.2 Codex Low + COMPOSER_1_5: Final[str] = "composer-1.5" # Composer 1.5 + COMPOSER_1: Final[str] = "composer-1" # Composer 1 + GPT_5_4_XHIGH: Final[str] = "gpt-5.4-xhigh" # GPT-5.4 Extra High + GPT_5_4_XHIGH_FAST: Final[str] = "gpt-5.4-xhigh-fast" # GPT-5.4 Extra High Fast + GPT_5_4_HIGH: Final[str] = "gpt-5.4-high" # GPT-5.4 High + GPT_5_4_HIGH_FAST: Final[str] = "gpt-5.4-high-fast" # GPT-5.4 High Fast + GPT_5_4_MEDIUM: Final[str] = "gpt-5.4-medium" # GPT-5.4 + GPT_5_4_MEDIUM_FAST: Final[str] = "gpt-5.4-medium-fast" # GPT-5.4 Fast + GPT_5_4_LOW: Final[str] = "gpt-5.4-low" # GPT-5.4 Low + GPT_5_3_CODEX_XHIGH: Final[str] = "gpt-5.3-codex-xhigh" # GPT-5.3 Codex Extra High + GPT_5_3_CODEX_XHIGH_FAST: Final[str] = "gpt-5.3-codex-xhigh-fast" # GPT-5.3 Codex Extra High Fast + GPT_5_3_CODEX_HIGH: Final[str] = "gpt-5.3-codex-high" # GPT-5.3 Codex High + GPT_5_3_CODEX_HIGH_FAST: Final[str] = "gpt-5.3-codex-high-fast" # GPT-5.3 Codex High Fast + GPT_5_3_CODEX: Final[str] = "gpt-5.3-codex" # GPT-5.3 Codex + GPT_5_3_CODEX_FAST: Final[str] = "gpt-5.3-codex-fast" # GPT-5.3 Codex Fast + GPT_5_3_CODEX_LOW: Final[str] = "gpt-5.3-codex-low" # GPT-5.3 Codex Low + GPT_5_3_CODEX_LOW_FAST: Final[str] = "gpt-5.3-codex-low-fast" # GPT-5.3 Codex Low Fast + GPT_5_3_CODEX_SPARK_PREVIEW: Final[str] = "gpt-5.3-codex-spark-preview" # GPT-5.3 Codex Spark GPT_5_2_CODEX_XHIGH: Final[str] = "gpt-5.2-codex-xhigh" # GPT-5.2 Codex Extra High - GPT_5_2_CODEX_FAST: Final[str] = "gpt-5.2-codex-fast" # GPT-5.2 Codex Fast + GPT_5_2_CODEX_XHIGH_FAST: Final[str] = "gpt-5.2-codex-xhigh-fast" # GPT-5.2 Codex Extra High Fast + GPT_5_2_CODEX_HIGH: Final[str] = "gpt-5.2-codex-high" # GPT-5.2 Codex High GPT_5_2_CODEX_HIGH_FAST: Final[str] = "gpt-5.2-codex-high-fast" # GPT-5.2 Codex High Fast + GPT_5_2_CODEX: Final[str] = "gpt-5.2-codex" # GPT-5.2 Codex + GPT_5_2_CODEX_FAST: Final[str] = "gpt-5.2-codex-fast" # GPT-5.2 Codex Fast + GPT_5_2_CODEX_LOW: Final[str] = "gpt-5.2-codex-low" # GPT-5.2 Codex Low GPT_5_2_CODEX_LOW_FAST: Final[str] = "gpt-5.2-codex-low-fast" # GPT-5.2 Codex Low Fast - GPT_5_2_CODEX_XHIGH_FAST: Final[str] = "gpt-5.2-codex-xhigh-fast" # GPT-5.2 Codex Extra High Fast - GPT_5_1_CODEX_MAX: Final[str] = "gpt-5.1-codex-max" # GPT-5.1 Codex Max - GPT_5_1_CODEX_MAX_HIGH: Final[str] = "gpt-5.1-codex-max-high" # GPT-5.1 Codex Max High GPT_5_2: Final[str] = "gpt-5.2" # GPT-5.2 GPT_5_2_HIGH: Final[str] = "gpt-5.2-high" # GPT-5.2 High + GPT_5_1_CODEX_MAX: Final[str] = "gpt-5.1-codex-max" # GPT-5.1 Codex Max + GPT_5_1_CODEX_MAX_HIGH: Final[str] = "gpt-5.1-codex-max-high" # GPT-5.1 Codex Max High + GPT_5_1_CODEX_MINI: Final[str] = "gpt-5.1-codex-mini" # GPT-5.1 Codex Mini GPT_5_1_HIGH: Final[str] = "gpt-5.1-high" # GPT-5.1 High + GEMINI_3_1_PRO: Final[str] = "gemini-3.1-pro" # Gemini 3.1 Pro GEMINI_3_PRO: Final[str] = "gemini-3-pro" # Gemini 3 Pro GEMINI_3_FLASH: Final[str] = "gemini-3-flash" # Gemini 3 Flash - COMPOSER_1: Final[str] = "composer-1" # Composer 1 GROK: Final[str] = "grok" # Grok + KIMI_K2_5: Final[str] = "kimi-k2.5" # Kimi K2.5 + + +class DroidModels: + """Droid model identifiers.""" + OPUS_4_6_FAST: Final[str] = "opus-4.6-fast" # Opus 4.6 Fast Mode (12x) (default) + OPUS_4_5: Final[str] = "opus-4.5" # Opus 4.5 (2x) + SONNET_4_5: Final[str] = "sonnet-4.5" # Sonnet 4.5 (1.2x) + HAIKU_4_5: Final[str] = "haiku-4.5" # Haiku 4.5 (0.4x) + GPT_5_2: Final[str] = "gpt-5.2" # GPT-5.2 (0.7x) + GPT_5_2_CODEX: Final[str] = "gpt-5.2-codex" # GPT-5.2 Codex (0.7x) + GEMINI_3_FLASH: Final[str] = "gemini-3-flash" # Gemini 3 Flash (0.2x) + DROID_CORE: Final[str] = "droid-core-glm-4.7" # Droid Core (GLM-4.7) (0.25x) + + +class OpencodeModels: + """OpenCode model identifiers.""" + OPENCODE_BIG_PICKLE: Final[str] = "opencode/big-pickle" # Big Pickle + OPENCODE_GPT_5_NANO: Final[str] = "opencode/gpt-5-nano" # GPT-5 Nano (OpenCode) + OPENCODE_MIMO_V2_FLASH_FREE: Final[str] = "opencode/mimo-v2-flash-free" # Mimo V2 Flash Free + OPENCODE_MINIMAX_M2_5_FREE: Final[str] = "opencode/minimax-m2.5-free" # MiniMax M2.5 Free + OPENAI_CODEX_MINI_LATEST: Final[str] = "openai/codex-mini-latest" # Codex Mini Latest + OPENAI_GPT_3_5_TURBO: Final[str] = "openai/gpt-3.5-turbo" # GPT-3.5 Turbo + OPENAI_GPT_4: Final[str] = "openai/gpt-4" # GPT-4 + OPENAI_GPT_4_TURBO: Final[str] = "openai/gpt-4-turbo" # GPT-4 Turbo + OPENAI_GPT_4_1: Final[str] = "openai/gpt-4.1" # GPT-4.1 + OPENAI_GPT_4_1_MINI: Final[str] = "openai/gpt-4.1-mini" # GPT-4.1 Mini + OPENAI_GPT_4_1_NANO: Final[str] = "openai/gpt-4.1-nano" # GPT-4.1 Nano + OPENAI_GPT_4O: Final[str] = "openai/gpt-4o" # GPT-4o + OPENAI_GPT_4O_2024_05_13: Final[str] = "openai/gpt-4o-2024-05-13" # GPT-4o (2024-05-13) + OPENAI_GPT_4O_2024_08_06: Final[str] = "openai/gpt-4o-2024-08-06" # GPT-4o (2024-08-06) + OPENAI_GPT_4O_2024_11_20: Final[str] = "openai/gpt-4o-2024-11-20" # GPT-4o (2024-11-20) + OPENAI_GPT_4O_MINI: Final[str] = "openai/gpt-4o-mini" # GPT-4o Mini + OPENAI_GPT_5: Final[str] = "openai/gpt-5" # GPT-5 + OPENAI_GPT_5_CODEX: Final[str] = "openai/gpt-5-codex" # GPT-5 Codex + OPENAI_GPT_5_MINI: Final[str] = "openai/gpt-5-mini" # GPT-5 Mini + OPENAI_GPT_5_NANO: Final[str] = "openai/gpt-5-nano" # GPT-5 Nano + OPENAI_GPT_5_PRO: Final[str] = "openai/gpt-5-pro" # GPT-5 Pro + OPENAI_GPT_5_1: Final[str] = "openai/gpt-5.1" # GPT-5.1 + OPENAI_GPT_5_1_CHAT_LATEST: Final[str] = "openai/gpt-5.1-chat-latest" # GPT-5.1 Chat Latest + OPENAI_GPT_5_1_CODEX: Final[str] = "openai/gpt-5.1-codex" # GPT-5.1 Codex + OPENAI_GPT_5_1_CODEX_MAX: Final[str] = "openai/gpt-5.1-codex-max" # GPT-5.1 Codex Max + OPENAI_GPT_5_1_CODEX_MINI: Final[str] = "openai/gpt-5.1-codex-mini" # GPT-5.1 Codex Mini + OPENAI_GPT_5_2: Final[str] = "openai/gpt-5.2" # GPT-5.2 (default) + OPENAI_GPT_5_2_CHAT_LATEST: Final[str] = "openai/gpt-5.2-chat-latest" # GPT-5.2 Chat Latest + OPENAI_GPT_5_2_CODEX: Final[str] = "openai/gpt-5.2-codex" # GPT-5.2 Codex + OPENAI_GPT_5_2_PRO: Final[str] = "openai/gpt-5.2-pro" # GPT-5.2 Pro + OPENAI_GPT_5_3_CODEX: Final[str] = "openai/gpt-5.3-codex" # GPT-5.3 Codex + OPENAI_GPT_5_3_CODEX_SPARK: Final[str] = "openai/gpt-5.3-codex-spark" # GPT-5.3 Codex Spark + OPENAI_GPT_5_4: Final[str] = "openai/gpt-5.4" # GPT-5.4 + OPENAI_GPT_5_4_PRO: Final[str] = "openai/gpt-5.4-pro" # GPT-5.4 Pro + OPENAI_O1: Final[str] = "openai/o1" # O1 + OPENAI_O1_MINI: Final[str] = "openai/o1-mini" # O1 Mini + OPENAI_O1_PREVIEW: Final[str] = "openai/o1-preview" # O1 Preview + OPENAI_O1_PRO: Final[str] = "openai/o1-pro" # O1 Pro + OPENAI_O3: Final[str] = "openai/o3" # O3 + OPENAI_O3_DEEP_RESEARCH: Final[str] = "openai/o3-deep-research" # O3 Deep Research + OPENAI_O3_MINI: Final[str] = "openai/o3-mini" # O3 Mini + OPENAI_O3_PRO: Final[str] = "openai/o3-pro" # O3 Pro + OPENAI_O4_MINI: Final[str] = "openai/o4-mini" # O4 Mini + OPENAI_O4_MINI_DEEP_RESEARCH: Final[str] = "openai/o4-mini-deep-research" # O4 Mini Deep Research class ModelOption(TypedDict): @@ -90,43 +180,127 @@ class ModelOption(TypedDict): ] CODEX_MODEL_OPTIONS: Final[List[ModelOption]] = [ - {"value": "gpt-5.2-codex", "label": "GPT-5.2 Codex — Frontier agentic coding model"}, - {"value": "gpt-5.3-codex", "label": "GPT-5.3 Codex — Latest frontier agentic coding model"}, + {"value": "gpt-5.4", "label": "GPT-5.4 — Latest frontier agentic coding model"}, + {"value": "gpt-5.3-codex", "label": "GPT-5.3 Codex — Frontier agentic coding model"}, {"value": "gpt-5.3-codex-spark", "label": "GPT-5.3 Codex Spark — Ultra-fast coding model"}, - {"value": "gpt-5.1-codex-max", "label": "GPT-5.1 Codex Max — Deep and fast reasoning"}, + {"value": "gpt-5.2-codex", "label": "GPT-5.2 Codex — Frontier agentic coding model"}, {"value": "gpt-5.2", "label": "GPT-5.2 — Frontier model, knowledge & reasoning"}, + {"value": "gpt-5.1-codex-max", "label": "GPT-5.1 Codex Max — Deep and fast reasoning"}, {"value": "gpt-5.1-codex-mini", "label": "GPT-5.1 Codex Mini — Cheaper, faster"}, ] GEMINI_MODEL_OPTIONS: Final[List[ModelOption]] = [ - {"value": "gemini-3-pro-preview", "label": "Gemini 3 Pro Preview"}, + {"value": "gemini-3.1-pro-preview", "label": "Gemini 3.1 Pro Preview"}, + {"value": "gemini-3-flash-preview", "label": "Gemini 3 Flash Preview"}, {"value": "gemini-2.5-pro", "label": "Gemini 2.5 Pro"}, {"value": "gemini-2.5-flash", "label": "Gemini 2.5 Flash"}, {"value": "gemini-2.5-flash-lite", "label": "Gemini 2.5 Flash Lite"}, ] CURSOR_MODEL_OPTIONS: Final[List[ModelOption]] = [ - {"value": "opus-4.5-thinking", "label": "Claude 4.5 Opus (Thinking)"}, + {"value": "opus-4.6-thinking", "label": "Claude 4.6 Opus (Thinking)"}, + {"value": "opus-4.6", "label": "Claude 4.6 Opus"}, {"value": "opus-4.5", "label": "Claude 4.5 Opus"}, + {"value": "opus-4.5-thinking", "label": "Claude 4.5 Opus (Thinking)"}, + {"value": "sonnet-4.6", "label": "Claude 4.6 Sonnet"}, + {"value": "sonnet-4.6-thinking", "label": "Claude 4.6 Sonnet (Thinking)"}, {"value": "sonnet-4.5", "label": "Claude 4.5 Sonnet"}, {"value": "sonnet-4.5-thinking", "label": "Claude 4.5 Sonnet (Thinking)"}, - {"value": "gpt-5.2-codex", "label": "GPT-5.2 Codex"}, - {"value": "gpt-5.2-codex-high", "label": "GPT-5.2 Codex High"}, - {"value": "gpt-5.2-codex-low", "label": "GPT-5.2 Codex Low"}, + {"value": "composer-1.5", "label": "Composer 1.5"}, + {"value": "composer-1", "label": "Composer 1"}, + {"value": "gpt-5.4-xhigh", "label": "GPT-5.4 Extra High"}, + {"value": "gpt-5.4-xhigh-fast", "label": "GPT-5.4 Extra High Fast"}, + {"value": "gpt-5.4-high", "label": "GPT-5.4 High"}, + {"value": "gpt-5.4-high-fast", "label": "GPT-5.4 High Fast"}, + {"value": "gpt-5.4-medium", "label": "GPT-5.4"}, + {"value": "gpt-5.4-medium-fast", "label": "GPT-5.4 Fast"}, + {"value": "gpt-5.4-low", "label": "GPT-5.4 Low"}, + {"value": "gpt-5.3-codex-xhigh", "label": "GPT-5.3 Codex Extra High"}, + {"value": "gpt-5.3-codex-xhigh-fast", "label": "GPT-5.3 Codex Extra High Fast"}, + {"value": "gpt-5.3-codex-high", "label": "GPT-5.3 Codex High"}, + {"value": "gpt-5.3-codex-high-fast", "label": "GPT-5.3 Codex High Fast"}, + {"value": "gpt-5.3-codex", "label": "GPT-5.3 Codex"}, + {"value": "gpt-5.3-codex-fast", "label": "GPT-5.3 Codex Fast"}, + {"value": "gpt-5.3-codex-low", "label": "GPT-5.3 Codex Low"}, + {"value": "gpt-5.3-codex-low-fast", "label": "GPT-5.3 Codex Low Fast"}, + {"value": "gpt-5.3-codex-spark-preview", "label": "GPT-5.3 Codex Spark"}, {"value": "gpt-5.2-codex-xhigh", "label": "GPT-5.2 Codex Extra High"}, - {"value": "gpt-5.2-codex-fast", "label": "GPT-5.2 Codex Fast"}, + {"value": "gpt-5.2-codex-xhigh-fast", "label": "GPT-5.2 Codex Extra High Fast"}, + {"value": "gpt-5.2-codex-high", "label": "GPT-5.2 Codex High"}, {"value": "gpt-5.2-codex-high-fast", "label": "GPT-5.2 Codex High Fast"}, + {"value": "gpt-5.2-codex", "label": "GPT-5.2 Codex"}, + {"value": "gpt-5.2-codex-fast", "label": "GPT-5.2 Codex Fast"}, + {"value": "gpt-5.2-codex-low", "label": "GPT-5.2 Codex Low"}, {"value": "gpt-5.2-codex-low-fast", "label": "GPT-5.2 Codex Low Fast"}, - {"value": "gpt-5.2-codex-xhigh-fast", "label": "GPT-5.2 Codex Extra High Fast"}, - {"value": "gpt-5.1-codex-max", "label": "GPT-5.1 Codex Max"}, - {"value": "gpt-5.1-codex-max-high", "label": "GPT-5.1 Codex Max High"}, {"value": "gpt-5.2", "label": "GPT-5.2"}, {"value": "gpt-5.2-high", "label": "GPT-5.2 High"}, + {"value": "gpt-5.1-codex-max", "label": "GPT-5.1 Codex Max"}, + {"value": "gpt-5.1-codex-max-high", "label": "GPT-5.1 Codex Max High"}, + {"value": "gpt-5.1-codex-mini", "label": "GPT-5.1 Codex Mini"}, {"value": "gpt-5.1-high", "label": "GPT-5.1 High"}, + {"value": "gemini-3.1-pro", "label": "Gemini 3.1 Pro"}, {"value": "gemini-3-pro", "label": "Gemini 3 Pro"}, {"value": "gemini-3-flash", "label": "Gemini 3 Flash"}, - {"value": "composer-1", "label": "Composer 1"}, {"value": "grok", "label": "Grok"}, + {"value": "kimi-k2.5", "label": "Kimi K2.5"}, +] + +DROID_MODEL_OPTIONS: Final[List[ModelOption]] = [ + {"value": "opus-4.6-fast", "label": "Opus 4.6 Fast Mode (12x)"}, + {"value": "opus-4.5", "label": "Opus 4.5 (2x)"}, + {"value": "sonnet-4.5", "label": "Sonnet 4.5 (1.2x)"}, + {"value": "haiku-4.5", "label": "Haiku 4.5 (0.4x)"}, + {"value": "gpt-5.2", "label": "GPT-5.2 (0.7x)"}, + {"value": "gpt-5.2-codex", "label": "GPT-5.2 Codex (0.7x)"}, + {"value": "gemini-3-flash", "label": "Gemini 3 Flash (0.2x)"}, + {"value": "droid-core-glm-4.7", "label": "Droid Core (GLM-4.7) (0.25x)"}, +] + +OPENCODE_MODEL_OPTIONS: Final[List[ModelOption]] = [ + {"value": "opencode/big-pickle", "label": "Big Pickle"}, + {"value": "opencode/gpt-5-nano", "label": "GPT-5 Nano (OpenCode)"}, + {"value": "opencode/mimo-v2-flash-free", "label": "Mimo V2 Flash Free"}, + {"value": "opencode/minimax-m2.5-free", "label": "MiniMax M2.5 Free"}, + {"value": "openai/codex-mini-latest", "label": "Codex Mini Latest"}, + {"value": "openai/gpt-3.5-turbo", "label": "GPT-3.5 Turbo"}, + {"value": "openai/gpt-4", "label": "GPT-4"}, + {"value": "openai/gpt-4-turbo", "label": "GPT-4 Turbo"}, + {"value": "openai/gpt-4.1", "label": "GPT-4.1"}, + {"value": "openai/gpt-4.1-mini", "label": "GPT-4.1 Mini"}, + {"value": "openai/gpt-4.1-nano", "label": "GPT-4.1 Nano"}, + {"value": "openai/gpt-4o", "label": "GPT-4o"}, + {"value": "openai/gpt-4o-2024-05-13", "label": "GPT-4o (2024-05-13)"}, + {"value": "openai/gpt-4o-2024-08-06", "label": "GPT-4o (2024-08-06)"}, + {"value": "openai/gpt-4o-2024-11-20", "label": "GPT-4o (2024-11-20)"}, + {"value": "openai/gpt-4o-mini", "label": "GPT-4o Mini"}, + {"value": "openai/gpt-5", "label": "GPT-5"}, + {"value": "openai/gpt-5-codex", "label": "GPT-5 Codex"}, + {"value": "openai/gpt-5-mini", "label": "GPT-5 Mini"}, + {"value": "openai/gpt-5-nano", "label": "GPT-5 Nano"}, + {"value": "openai/gpt-5-pro", "label": "GPT-5 Pro"}, + {"value": "openai/gpt-5.1", "label": "GPT-5.1"}, + {"value": "openai/gpt-5.1-chat-latest", "label": "GPT-5.1 Chat Latest"}, + {"value": "openai/gpt-5.1-codex", "label": "GPT-5.1 Codex"}, + {"value": "openai/gpt-5.1-codex-max", "label": "GPT-5.1 Codex Max"}, + {"value": "openai/gpt-5.1-codex-mini", "label": "GPT-5.1 Codex Mini"}, + {"value": "openai/gpt-5.2", "label": "GPT-5.2"}, + {"value": "openai/gpt-5.2-chat-latest", "label": "GPT-5.2 Chat Latest"}, + {"value": "openai/gpt-5.2-codex", "label": "GPT-5.2 Codex"}, + {"value": "openai/gpt-5.2-pro", "label": "GPT-5.2 Pro"}, + {"value": "openai/gpt-5.3-codex", "label": "GPT-5.3 Codex"}, + {"value": "openai/gpt-5.3-codex-spark", "label": "GPT-5.3 Codex Spark"}, + {"value": "openai/gpt-5.4", "label": "GPT-5.4"}, + {"value": "openai/gpt-5.4-pro", "label": "GPT-5.4 Pro"}, + {"value": "openai/o1", "label": "O1"}, + {"value": "openai/o1-mini", "label": "O1 Mini"}, + {"value": "openai/o1-preview", "label": "O1 Preview"}, + {"value": "openai/o1-pro", "label": "O1 Pro"}, + {"value": "openai/o3", "label": "O3"}, + {"value": "openai/o3-deep-research", "label": "O3 Deep Research"}, + {"value": "openai/o3-mini", "label": "O3 Mini"}, + {"value": "openai/o3-pro", "label": "O3 Pro"}, + {"value": "openai/o4-mini", "label": "O4 Mini"}, + {"value": "openai/o4-mini-deep-research", "label": "O4 Mini Deep Research"}, ] class Models: @@ -135,6 +309,8 @@ class Models: Codex = CodexModels Gemini = GeminiModels Cursor = CursorModels + Droid = DroidModels + Opencode = OpencodeModels class ModelOptions: @@ -143,6 +319,8 @@ class ModelOptions: Codex = CODEX_MODEL_OPTIONS Gemini = GEMINI_MODEL_OPTIONS Cursor = CURSOR_MODEL_OPTIONS + Droid = DROID_MODEL_OPTIONS + Opencode = OPENCODE_MODEL_OPTIONS class SwarmPatterns: @@ -161,36 +339,50 @@ class SwarmPatterns: DEFAULT_MODELS: Final[dict] = { "claude": "sonnet", - "codex": "gpt-5.2-codex", - "gemini": "gemini-2.5-pro", - "cursor": "opus-4.5-thinking", + "codex": "gpt-5.4", + "gemini": "gemini-3.1-pro-preview", + "cursor": "opus-4.6-thinking", + "droid": "opus-4.6-fast", + "opencode": "openai/gpt-5.2", } CLI_REGISTRY: Final[dict] = { "claude": { "name": "Claude Code", "package": "@anthropic-ai/claude-code", - "version": "2.1.50", + "version": "2.1.72", "install": "npm install -g @anthropic-ai/claude-code", }, "codex": { "name": "Codex CLI", "package": "@openai/codex", - "version": "0.104.0", + "version": "0.114.0", "install": "npm install -g @openai/codex", }, "gemini": { "name": "Gemini CLI", "package": "@google/gemini-cli", - "version": "0.29.5", + "version": "0.33.0", "install": "npm install -g @google/gemini-cli", }, "cursor": { "name": "Cursor", "package": "cursor", - "version": "0.48.6", + "version": "2026.02.27-e7d2ef6", "install": "Download from cursor.com", }, + "droid": { + "name": "Droid", + "package": "droid", + "version": "0.1.0", + "install": "Download from droid.dev", + }, + "opencode": { + "name": "OpenCode", + "package": "opencode-ai", + "version": "1.2.24", + "install": "npm install -g opencode-ai", + }, "aider": { "name": "Aider", "package": "aider-chat", diff --git a/packages/shared/cli-registry.yaml b/packages/shared/cli-registry.yaml index dd58823d1..357825850 100644 --- a/packages/shared/cli-registry.yaml +++ b/packages/shared/cli-registry.yaml @@ -12,7 +12,7 @@ clis: name: "Claude Code" package: "@anthropic-ai/claude-code" npm_link: "https://www.npmjs.com/package/@anthropic-ai" - version: "2.1.50" + version: "2.1.72" install: "npm install -g @anthropic-ai/claude-code" models: sonnet: @@ -30,25 +30,28 @@ clis: name: "Codex CLI" package: "@openai/codex" npm_link: "https://www.npmjs.com/package/@openai/codex" - version: "0.104.0" + version: "0.114.0" install: "npm install -g @openai/codex" models: - gpt_5_2_codex: - id: "gpt-5.2-codex" - label: "GPT-5.2 Codex — Frontier agentic coding model" + gpt_5_4: + id: "gpt-5.4" + label: "GPT-5.4 — Latest frontier agentic coding model" default: true gpt_5_3_codex: id: "gpt-5.3-codex" - label: "GPT-5.3 Codex — Latest frontier agentic coding model" + label: "GPT-5.3 Codex — Frontier agentic coding model" gpt_5_3_codex_spark: id: "gpt-5.3-codex-spark" label: "GPT-5.3 Codex Spark — Ultra-fast coding model" - gpt_5_1_codex_max: - id: "gpt-5.1-codex-max" - label: "GPT-5.1 Codex Max — Deep and fast reasoning" + gpt_5_2_codex: + id: "gpt-5.2-codex" + label: "GPT-5.2 Codex — Frontier agentic coding model" gpt_5_2: id: "gpt-5.2" label: "GPT-5.2 — Frontier model, knowledge & reasoning" + gpt_5_1_codex_max: + id: "gpt-5.1-codex-max" + label: "GPT-5.1 Codex Max — Deep and fast reasoning" gpt_5_1_codex_mini: id: "gpt-5.1-codex-mini" label: "GPT-5.1 Codex Mini — Cheaper, faster" @@ -57,16 +60,19 @@ clis: name: "Gemini CLI" package: "@google/gemini-cli" npm_link: "https://www.npmjs.com/package/@google/gemini-cli" - version: "0.29.5" + version: "0.33.0" install: "npm install -g @google/gemini-cli" models: - gemini_3_pro_preview: - id: "gemini-3-pro-preview" - label: "Gemini 3 Pro Preview" + gemini_3_1_pro_preview: + id: "gemini-3.1-pro-preview" + label: "Gemini 3.1 Pro Preview" + default: true + gemini_3_flash_preview: + id: "gemini-3-flash-preview" + label: "Gemini 3 Flash Preview" gemini_2_5_pro: id: "gemini-2.5-pro" label: "Gemini 2.5 Pro" - default: true gemini_2_5_flash: id: "gemini-2.5-flash" label: "Gemini 2.5 Flash" @@ -77,73 +83,318 @@ clis: cursor: name: "Cursor" package: "cursor" - version: "0.48.6" + version: "2026.02.27-e7d2ef6" install: "Download from cursor.com" models: - opus_4_5_thinking: - id: "opus-4.5-thinking" - label: "Claude 4.5 Opus (Thinking)" + opus_4_6_thinking: + id: "opus-4.6-thinking" + label: "Claude 4.6 Opus (Thinking)" default: true + opus_4_6: + id: "opus-4.6" + label: "Claude 4.6 Opus" opus_4_5: id: "opus-4.5" label: "Claude 4.5 Opus" + opus_4_5_thinking: + id: "opus-4.5-thinking" + label: "Claude 4.5 Opus (Thinking)" + sonnet_4_6: + id: "sonnet-4.6" + label: "Claude 4.6 Sonnet" + sonnet_4_6_thinking: + id: "sonnet-4.6-thinking" + label: "Claude 4.6 Sonnet (Thinking)" sonnet_4_5: id: "sonnet-4.5" label: "Claude 4.5 Sonnet" sonnet_4_5_thinking: id: "sonnet-4.5-thinking" label: "Claude 4.5 Sonnet (Thinking)" - gpt_5_2_codex: - id: "gpt-5.2-codex" - label: "GPT-5.2 Codex" - gpt_5_2_codex_high: - id: "gpt-5.2-codex-high" - label: "GPT-5.2 Codex High" - gpt_5_2_codex_low: - id: "gpt-5.2-codex-low" - label: "GPT-5.2 Codex Low" + composer_1_5: + id: "composer-1.5" + label: "Composer 1.5" + composer_1: + id: "composer-1" + label: "Composer 1" + gpt_5_4_xhigh: + id: "gpt-5.4-xhigh" + label: "GPT-5.4 Extra High" + gpt_5_4_xhigh_fast: + id: "gpt-5.4-xhigh-fast" + label: "GPT-5.4 Extra High Fast" + gpt_5_4_high: + id: "gpt-5.4-high" + label: "GPT-5.4 High" + gpt_5_4_high_fast: + id: "gpt-5.4-high-fast" + label: "GPT-5.4 High Fast" + gpt_5_4_medium: + id: "gpt-5.4-medium" + label: "GPT-5.4" + gpt_5_4_medium_fast: + id: "gpt-5.4-medium-fast" + label: "GPT-5.4 Fast" + gpt_5_4_low: + id: "gpt-5.4-low" + label: "GPT-5.4 Low" + gpt_5_3_codex_xhigh: + id: "gpt-5.3-codex-xhigh" + label: "GPT-5.3 Codex Extra High" + gpt_5_3_codex_xhigh_fast: + id: "gpt-5.3-codex-xhigh-fast" + label: "GPT-5.3 Codex Extra High Fast" + gpt_5_3_codex_high: + id: "gpt-5.3-codex-high" + label: "GPT-5.3 Codex High" + gpt_5_3_codex_high_fast: + id: "gpt-5.3-codex-high-fast" + label: "GPT-5.3 Codex High Fast" + gpt_5_3_codex: + id: "gpt-5.3-codex" + label: "GPT-5.3 Codex" + gpt_5_3_codex_fast: + id: "gpt-5.3-codex-fast" + label: "GPT-5.3 Codex Fast" + gpt_5_3_codex_low: + id: "gpt-5.3-codex-low" + label: "GPT-5.3 Codex Low" + gpt_5_3_codex_low_fast: + id: "gpt-5.3-codex-low-fast" + label: "GPT-5.3 Codex Low Fast" + gpt_5_3_codex_spark_preview: + id: "gpt-5.3-codex-spark-preview" + label: "GPT-5.3 Codex Spark" gpt_5_2_codex_xhigh: id: "gpt-5.2-codex-xhigh" label: "GPT-5.2 Codex Extra High" - gpt_5_2_codex_fast: - id: "gpt-5.2-codex-fast" - label: "GPT-5.2 Codex Fast" + gpt_5_2_codex_xhigh_fast: + id: "gpt-5.2-codex-xhigh-fast" + label: "GPT-5.2 Codex Extra High Fast" + gpt_5_2_codex_high: + id: "gpt-5.2-codex-high" + label: "GPT-5.2 Codex High" gpt_5_2_codex_high_fast: id: "gpt-5.2-codex-high-fast" label: "GPT-5.2 Codex High Fast" + gpt_5_2_codex: + id: "gpt-5.2-codex" + label: "GPT-5.2 Codex" + gpt_5_2_codex_fast: + id: "gpt-5.2-codex-fast" + label: "GPT-5.2 Codex Fast" + gpt_5_2_codex_low: + id: "gpt-5.2-codex-low" + label: "GPT-5.2 Codex Low" gpt_5_2_codex_low_fast: id: "gpt-5.2-codex-low-fast" label: "GPT-5.2 Codex Low Fast" - gpt_5_2_codex_xhigh_fast: - id: "gpt-5.2-codex-xhigh-fast" - label: "GPT-5.2 Codex Extra High Fast" - gpt_5_1_codex_max: - id: "gpt-5.1-codex-max" - label: "GPT-5.1 Codex Max" - gpt_5_1_codex_max_high: - id: "gpt-5.1-codex-max-high" - label: "GPT-5.1 Codex Max High" gpt_5_2: id: "gpt-5.2" label: "GPT-5.2" gpt_5_2_high: id: "gpt-5.2-high" label: "GPT-5.2 High" + gpt_5_1_codex_max: + id: "gpt-5.1-codex-max" + label: "GPT-5.1 Codex Max" + gpt_5_1_codex_max_high: + id: "gpt-5.1-codex-max-high" + label: "GPT-5.1 Codex Max High" + gpt_5_1_codex_mini: + id: "gpt-5.1-codex-mini" + label: "GPT-5.1 Codex Mini" gpt_5_1_high: id: "gpt-5.1-high" label: "GPT-5.1 High" + gemini_3_1_pro: + id: "gemini-3.1-pro" + label: "Gemini 3.1 Pro" gemini_3_pro: id: "gemini-3-pro" label: "Gemini 3 Pro" gemini_3_flash: id: "gemini-3-flash" label: "Gemini 3 Flash" - composer_1: - id: "composer-1" - label: "Composer 1" grok: id: "grok" label: "Grok" + kimi_k2_5: + id: "kimi-k2.5" + label: "Kimi K2.5" + + droid: + name: "Droid" + package: "droid" + version: "0.1.0" + install: "Download from droid.dev" + models: + opus_4_6_fast: + id: "opus-4.6-fast" + label: "Opus 4.6 Fast Mode (12x)" + default: true + opus_4_5: + id: "opus-4.5" + label: "Opus 4.5 (2x)" + sonnet_4_5: + id: "sonnet-4.5" + label: "Sonnet 4.5 (1.2x)" + haiku_4_5: + id: "haiku-4.5" + label: "Haiku 4.5 (0.4x)" + gpt_5_2: + id: "gpt-5.2" + label: "GPT-5.2 (0.7x)" + gpt_5_2_codex: + id: "gpt-5.2-codex" + label: "GPT-5.2 Codex (0.7x)" + gemini_3_flash: + id: "gemini-3-flash" + label: "Gemini 3 Flash (0.2x)" + droid_core: + id: "droid-core-glm-4.7" + label: "Droid Core (GLM-4.7) (0.25x)" + + opencode: + name: "OpenCode" + package: "opencode-ai" + npm_link: "https://www.npmjs.com/package/opencode-ai" + version: "1.2.24" + install: "npm install -g opencode-ai" + models: + opencode_big_pickle: + id: "opencode/big-pickle" + label: "Big Pickle" + opencode_gpt_5_nano: + id: "opencode/gpt-5-nano" + label: "GPT-5 Nano (OpenCode)" + opencode_mimo_v2_flash_free: + id: "opencode/mimo-v2-flash-free" + label: "Mimo V2 Flash Free" + opencode_minimax_m2_5_free: + id: "opencode/minimax-m2.5-free" + label: "MiniMax M2.5 Free" + openai_codex_mini_latest: + id: "openai/codex-mini-latest" + label: "Codex Mini Latest" + openai_gpt_3_5_turbo: + id: "openai/gpt-3.5-turbo" + label: "GPT-3.5 Turbo" + openai_gpt_4: + id: "openai/gpt-4" + label: "GPT-4" + openai_gpt_4_turbo: + id: "openai/gpt-4-turbo" + label: "GPT-4 Turbo" + openai_gpt_4_1: + id: "openai/gpt-4.1" + label: "GPT-4.1" + openai_gpt_4_1_mini: + id: "openai/gpt-4.1-mini" + label: "GPT-4.1 Mini" + openai_gpt_4_1_nano: + id: "openai/gpt-4.1-nano" + label: "GPT-4.1 Nano" + openai_gpt_4o: + id: "openai/gpt-4o" + label: "GPT-4o" + openai_gpt_4o_2024_05_13: + id: "openai/gpt-4o-2024-05-13" + label: "GPT-4o (2024-05-13)" + openai_gpt_4o_2024_08_06: + id: "openai/gpt-4o-2024-08-06" + label: "GPT-4o (2024-08-06)" + openai_gpt_4o_2024_11_20: + id: "openai/gpt-4o-2024-11-20" + label: "GPT-4o (2024-11-20)" + openai_gpt_4o_mini: + id: "openai/gpt-4o-mini" + label: "GPT-4o Mini" + openai_gpt_5: + id: "openai/gpt-5" + label: "GPT-5" + openai_gpt_5_codex: + id: "openai/gpt-5-codex" + label: "GPT-5 Codex" + openai_gpt_5_mini: + id: "openai/gpt-5-mini" + label: "GPT-5 Mini" + openai_gpt_5_nano: + id: "openai/gpt-5-nano" + label: "GPT-5 Nano" + openai_gpt_5_pro: + id: "openai/gpt-5-pro" + label: "GPT-5 Pro" + openai_gpt_5_1: + id: "openai/gpt-5.1" + label: "GPT-5.1" + openai_gpt_5_1_chat_latest: + id: "openai/gpt-5.1-chat-latest" + label: "GPT-5.1 Chat Latest" + openai_gpt_5_1_codex: + id: "openai/gpt-5.1-codex" + label: "GPT-5.1 Codex" + openai_gpt_5_1_codex_max: + id: "openai/gpt-5.1-codex-max" + label: "GPT-5.1 Codex Max" + openai_gpt_5_1_codex_mini: + id: "openai/gpt-5.1-codex-mini" + label: "GPT-5.1 Codex Mini" + openai_gpt_5_2: + id: "openai/gpt-5.2" + label: "GPT-5.2" + default: true + openai_gpt_5_2_chat_latest: + id: "openai/gpt-5.2-chat-latest" + label: "GPT-5.2 Chat Latest" + openai_gpt_5_2_codex: + id: "openai/gpt-5.2-codex" + label: "GPT-5.2 Codex" + openai_gpt_5_2_pro: + id: "openai/gpt-5.2-pro" + label: "GPT-5.2 Pro" + openai_gpt_5_3_codex: + id: "openai/gpt-5.3-codex" + label: "GPT-5.3 Codex" + openai_gpt_5_3_codex_spark: + id: "openai/gpt-5.3-codex-spark" + label: "GPT-5.3 Codex Spark" + openai_gpt_5_4: + id: "openai/gpt-5.4" + label: "GPT-5.4" + openai_gpt_5_4_pro: + id: "openai/gpt-5.4-pro" + label: "GPT-5.4 Pro" + openai_o1: + id: "openai/o1" + label: "O1" + openai_o1_mini: + id: "openai/o1-mini" + label: "O1 Mini" + openai_o1_preview: + id: "openai/o1-preview" + label: "O1 Preview" + openai_o1_pro: + id: "openai/o1-pro" + label: "O1 Pro" + openai_o3: + id: "openai/o3" + label: "O3" + openai_o3_deep_research: + id: "openai/o3-deep-research" + label: "O3 Deep Research" + openai_o3_mini: + id: "openai/o3-mini" + label: "O3 Mini" + openai_o3_pro: + id: "openai/o3-pro" + label: "O3 Pro" + openai_o4_mini: + id: "openai/o4-mini" + label: "O4 Mini" + openai_o4_mini_deep_research: + id: "openai/o4-mini-deep-research" + label: "O4 Mini Deep Research" aider: name: "Aider" diff --git a/scripts/postinstall.js b/scripts/postinstall.js index c39d28f07..c05cba81a 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -373,6 +373,112 @@ async function installBrokerBinary() { return false; } +/** + * Install the standalone dashboard-server binary. + * + * The curl-based installer downloads this binary, but npm install does not. + * This brings the npm install path to parity so `agent-relay up` can start + * the dashboard without falling back to `npx @agent-relay/dashboard-server@latest`. + */ +async function installDashboardBinary() { + const homeDir = os.homedir(); + const targetPath = path.join(homeDir, '.local', 'bin', 'relay-dashboard-server'); + + // Already installed? + if (fs.existsSync(targetPath)) { + try { + execSync(`"${targetPath}" --version`, { stdio: 'pipe' }); + info('Dashboard server binary already installed'); + return true; + } catch { + // Binary exists but broken — reinstall + } + } + + const platform = os.platform(); + const arch = os.arch(); + const platformMap = { darwin: 'darwin', linux: 'linux' }; + const archMap = { arm64: 'arm64', x64: 'x64' }; + const targetPlatform = platformMap[platform]; + const targetArch = archMap[arch]; + + if (!targetPlatform || !targetArch) { + info(`No prebuilt dashboard binary for ${platform}-${arch}`); + return false; + } + + const binaryName = `relay-dashboard-server-${targetPlatform}-${targetArch}`; + const downloadUrl = `https://github.com/AgentWorkforce/relay-dashboard/releases/latest/download/${binaryName}`; + + info(`Downloading dashboard-server binary...`); + try { + fs.mkdirSync(path.dirname(targetPath), { recursive: true }); + await downloadBinary(downloadUrl, targetPath); + fs.chmodSync(targetPath, 0o755); + resignBinaryForMacOS(targetPath); + success('Downloaded dashboard-server binary'); + return true; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + info(`Dashboard binary not available (${message}) — will use npx fallback`); + return false; + } +} + +/** + * Install the relay-acp binary (Zed editor integration). + * + * The curl-based installer downloads this binary, but npm install does not. + * This brings the npm install path to parity. + */ +async function installRelayAcpBinary() { + const homeDir = os.homedir(); + const targetPath = path.join(homeDir, '.local', 'bin', 'relay-acp'); + + // Already installed and functional? + if (fs.existsSync(targetPath)) { + try { + execSync(`"${targetPath}" --version`, { stdio: 'pipe' }); + info('relay-acp binary already installed'); + return true; + } catch { + // Binary exists but broken or outdated — reinstall + } + } + + const platform = os.platform(); + const arch = os.arch(); + const platformMap = { darwin: 'darwin', linux: 'linux' }; + const archMap = { arm64: 'arm64', x64: 'x64' }; + const targetPlatform = platformMap[platform]; + const targetArch = archMap[arch]; + + if (!targetPlatform || !targetArch) { + return false; + } + + const pkgRoot = getPackageRoot(); + const version = getPackageVersion(pkgRoot); + if (!version) return false; + + const binaryName = `relay-acp-${targetPlatform}-${targetArch}`; + const downloadUrl = `https://github.com/AgentWorkforce/relay/releases/download/v${version}/${binaryName}`; + + info('Downloading relay-acp binary (Zed editor integration)...'); + try { + fs.mkdirSync(path.dirname(targetPath), { recursive: true }); + await downloadBinary(downloadUrl, targetPath); + fs.chmodSync(targetPath, 0o755); + resignBinaryForMacOS(targetPath); + success('Downloaded relay-acp binary'); + return true; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + info(`relay-acp binary not available (${message}) — Zed integration requires manual install`); + return false; + } +} + /** * Setup workspace package symlinks for global/bundled installs. * @@ -639,9 +745,20 @@ async function main() { // Always install dashboard dependencies (needed for build) installDashboardDeps(); + // Install dashboard-server and relay-acp binaries for parity with curl installer + const hasDashboardBinary = await installDashboardBinary(); + const hasAcpBinary = await installRelayAcpBinary(); + // Always print diagnostics (even in CI) logPostinstallDiagnostics(hasBrokerBinary, sqliteStatus, linkResult); + if (hasDashboardBinary) { + console.log('✓ dashboard-server binary installed'); + } + if (hasAcpBinary) { + console.log('✓ relay-acp binary installed (Zed editor integration)'); + } + if (!hasBrokerBinary) { warn('agent-relay-broker binary not available'); info('Agent spawning will not work without the broker binary.'); diff --git a/src/main.rs b/src/main.rs index 0e5c74a0e..1ef127a10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2351,21 +2351,34 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { "received relaycast ws event" ); - if matches!(ws_type, "agent.spawn_requested" | "agent.release_requested") { - if let Some(control_dedup_key) = - relaycast_ws_control_dedup_key(&workspace_id, ws_type, &ws_value) - { - if !dedup.insert_if_new(&control_dedup_key, Instant::now()) { - tracing::info!( - ws_type = %ws_type, - workspace_id = %workspace_id, - "dropping duplicate relaycast control event" - ); - continue; - } + let control_dedup_key = if matches!( + ws_type, + "agent.spawn_requested" | "agent.release_requested" + ) { + relaycast_ws_control_dedup_key(&workspace_id, ws_type, &ws_value) + } else { + None + }; + + if let Some(ref control_dedup_key) = control_dedup_key { + if !dedup.insert_if_new(control_dedup_key, Instant::now()) { + tracing::info!( + ws_type = %ws_type, + workspace_id = %workspace_id, + "dropping duplicate relaycast control event" + ); + continue; } } + if matches!(ws_type, "agent.spawn_requested" | "agent.release_requested") { + if let Err(ref deser_err) = serde_json::from_value::(ws_value.clone()) { + eprintln!( + "[agent-relay] WARNING: failed to deserialize {} event: {}", + ws_type, deser_err + ); + } + } if let Ok(ws_event) = serde_json::from_value::(ws_value.clone()) { match ws_event { WsEvent::AgentReleaseRequested(event) => { @@ -2447,6 +2460,7 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { } WsEvent::AgentSpawnRequested(event) => { let name = event.agent.name; + eprintln!("[agent-relay] received spawn request for '{}' (cli: {})", name, event.agent.cli); if is_relaycast_self_control_target( &name, &workspace_self_name, @@ -2456,16 +2470,22 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { worker = %name, "ignoring relaycast spawn request for broker self" ); + eprintln!("[agent-relay] ignoring spawn request for '{}' (broker self)", name); continue; } let local_spawn_echo_key = - format!("control:{workspace_id}:agent.spawn_requested:{name}"); - if !dedup.insert_if_new(&local_spawn_echo_key, Instant::now()) { + relaycast_spawn_control_dedup_key(&workspace_id, &name); + if relaycast_ws_should_apply_local_spawn_echo_dedup( + control_dedup_key.as_deref(), + &local_spawn_echo_key, + ) && !dedup.insert_if_new(&local_spawn_echo_key, Instant::now()) + { tracing::info!( worker = %name, workspace_id = %workspace_id, "dropping duplicate/local relaycast spawn request" ); + eprintln!("[agent-relay] dropping duplicate spawn request for '{}'", name); continue; } let cli = event.agent.cli; @@ -2581,6 +2601,155 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { } _ => {} } + } else if ws_type == "agent.spawn_requested" { + // Fallback: the SDK failed to deserialize the event (e.g. missing + // fields like `already_existed` or `task: null`). Extract the + // spawn info directly from the raw JSON so we don't silently + // drop the request. + let agent_obj = ws_value.get("agent"); + let name = agent_obj + .and_then(|a| a.get("name")) + .and_then(Value::as_str) + .unwrap_or("") + .to_string(); + let cli = agent_obj + .and_then(|a| a.get("cli")) + .and_then(Value::as_str) + .unwrap_or("claude") + .to_string(); + let task = agent_obj + .and_then(|a| a.get("task")) + .and_then(Value::as_str) + .unwrap_or("") + .to_string(); + let channel = agent_obj + .and_then(|a| a.get("channel")) + .and_then(Value::as_str) + .map(String::from); + + if !name.is_empty() { + eprintln!("[agent-relay] handling spawn request for '{}' via JSON fallback (cli: {})", name, cli); + + if is_relaycast_self_control_target( + &name, + &workspace_self_name, + &workspace_self_names, + ) { + eprintln!("[agent-relay] ignoring spawn request for '{}' (broker self)", name); + } else { + let local_spawn_echo_key = + relaycast_spawn_control_dedup_key(&workspace_id, &name); + let should_dedup = relaycast_ws_should_apply_local_spawn_echo_dedup( + control_dedup_key.as_deref(), + &local_spawn_echo_key, + ); + // Always insert the local echo key for consistency with the primary path + let is_new = dedup.insert_if_new(&local_spawn_echo_key, Instant::now()); + if !should_dedup || is_new + { + let channels = channel + .as_deref() + .map(|ch| { + let mut chs = default_spawn_channels(); + if !chs.contains(&ch.to_string()) { + chs.push(ch.to_string()); + } + chs + }) + .unwrap_or_else(default_spawn_channels); + let spec = AgentSpec { + name: name.clone(), + runtime: AgentRuntime::Pty, + provider: None, + cli: Some(cli.clone()), + model: None, + cwd: None, + team: None, + shadow_of: None, + shadow_mode: None, + args: vec![], + channels: channels.clone(), + restart_policy: None, + }; + let spec_for_state = spec.clone(); + let task_opt = Some(task).filter(|v| !v.trim().is_empty()); + let effective_task = normalize_initial_task(task_opt.clone()); + + let worker_relay_key = relaycast_ws_spawn_token(&ws_value); + + match workers.spawn( + spec, + Some("Relaycast".to_string()), + None, + worker_relay_key.clone(), + false, + Some(workspace_id.clone()), + ).await { + Ok(()) => { + if let Some(ref task_text) = effective_task { + workers.initial_tasks.insert(name.clone(), task_text.clone()); + } + agent_spawn_count += 1; + telemetry.track(TelemetryEvent::AgentSpawn { + cli: cli.clone(), + runtime: "pty".to_string(), + }); + let pid = workers.worker_pid(&name).unwrap_or(0); + state.agents.insert( + name.clone(), + PersistedAgent { + runtime: AgentRuntime::Pty, + parent: Some("Relaycast".to_string()), + channels, + pid: workers.worker_pid(&name), + started_at: Some( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(), + ), + spec: Some(spec_for_state), + restart_policy: None, + initial_task: effective_task, + }, + ); + if paths.persist { let _ = state.save(&paths.state); } + let _ = send_event( + &sdk_out_tx, + json!({ + "kind": "agent_spawned", + "name": name, + "runtime": "pty", + "cli": cli, + "pid": pid, + "source": "relaycast_ws_fallback", + "pre_registered": worker_relay_key.is_some(), + }), + ).await; + publish_agent_state_transition( + &workspace_state.ws_control_tx, + &name, + "spawned", + Some("relaycast_spawn"), + ) + .await; + eprintln!("[agent-relay] spawned worker '{}' via relaycast (JSON fallback)", name); + } + Err(e) => { + let msg = e.to_string(); + if !msg.contains("already exists") { + eprintln!("[agent-relay] failed to spawn '{}': {}", name, e); + } + } + } + } else { + eprintln!("[agent-relay] dropping duplicate spawn request for '{}' (fallback)", name); + } + } + } + // Don't fall through to map_ws_event for control events + // handled by the JSON fallback path. + continue; } // Preserve the raw channel from the WS event for thread replies. @@ -3893,6 +4062,30 @@ async fn handle_sdk_frame( None, ) .await?; + + // Subscribe the broker's WebSocket to any custom channels the + // spawned agent needs so cloud-routed messages reach the broker. + if !payload.agent.channels.is_empty() { + let spawn_channels = payload.agent.channels.clone(); + for workspace in workspaces { + if let Err(error) = workspace + .http_client + .ensure_extra_channels(&spawn_channels) + .await + { + tracing::warn!( + workspace_id = %workspace.workspace_id, + error = %error, + "failed to ensure extra channels for spawned agent" + ); + } + let _ = workspace + .ws_control_tx + .send(WsControl::Subscribe(spawn_channels.clone())) + .await; + } + } + note_local_spawn_control_dedup( dedup, default_workspace_id.or_else(|| { @@ -5273,6 +5466,17 @@ fn relaycast_ws_spawn_token(value: &Value) -> Option { ) } +fn relaycast_spawn_control_dedup_key(workspace_id: &str, identity: &str) -> String { + format!("control:{workspace_id}:agent.spawn_requested:{identity}") +} + +fn relaycast_ws_should_apply_local_spawn_echo_dedup( + control_dedup_key: Option<&str>, + local_spawn_echo_key: &str, +) -> bool { + control_dedup_key != Some(local_spawn_echo_key) +} + fn note_local_spawn_control_dedup( dedup: &mut DedupCache, workspace_id: Option<&str>, @@ -5284,11 +5488,11 @@ fn note_local_spawn_control_dedup( }; let agent_name = agent_name.trim(); if !agent_name.is_empty() { - let key = format!("control:{workspace_id}:agent.spawn_requested:{agent_name}"); + let key = relaycast_spawn_control_dedup_key(workspace_id, agent_name); dedup.insert_if_new(&key, Instant::now()); } if let Some(relay_key) = relay_key.map(str::trim).filter(|value| !value.is_empty()) { - let key = format!("control:{workspace_id}:agent.spawn_requested:{relay_key}"); + let key = relaycast_spawn_control_dedup_key(workspace_id, relay_key); dedup.insert_if_new(&key, Instant::now()); } } @@ -6019,7 +6223,7 @@ impl BrokerState { mod tests { use std::{ collections::{BTreeSet, HashMap, HashSet}, - time::Instant, + time::{Duration, Instant}, }; use crate::helpers::{format_injection, terminal_query_responses}; @@ -6034,10 +6238,13 @@ mod tests { http_api_relaycast_send_timeout, is_auto_suggestion, is_bypass_selection_menu, is_in_editor_mode, is_relaycast_self_control_target, is_unknown_worker_error_message, normalize_channel, normalize_initial_task, normalize_sender, - relaycast_ws_control_dedup_key, relaycast_ws_spawn_token, sender_is_dashboard_label, - should_clear_pending_delivery_for_event, strip_ansi, PendingDelivery, TerminalQueryParser, + relaycast_spawn_control_dedup_key, relaycast_ws_control_dedup_key, + relaycast_ws_should_apply_local_spawn_echo_dedup, relaycast_ws_spawn_token, + sender_is_dashboard_label, should_clear_pending_delivery_for_event, strip_ansi, + PendingDelivery, TerminalQueryParser, }; use crate::helpers::floor_char_boundary; + use relay_broker::dedup::DedupCache; use relay_broker::relaycast_ws::{ format_worker_preregistration_error, RelaycastRegistrationError, }; @@ -6199,6 +6406,57 @@ mod tests { ); } + #[test] + fn relaycast_ws_spawn_name_only_control_key_skips_second_name_dedup() { + let value = json!({ + "type": "agent.spawn_requested", + "agent": { + "name": "worker-a", + "cli": "claude", + "task": "Ship it" + } + }); + + let control_key = relaycast_ws_control_dedup_key("ws_1", "agent.spawn_requested", &value) + .expect("control dedup key"); + let local_key = relaycast_spawn_control_dedup_key("ws_1", "worker-a"); + + assert_eq!(control_key, local_key); + assert!(!relaycast_ws_should_apply_local_spawn_echo_dedup( + Some(control_key.as_str()), + &local_key + )); + } + + #[test] + fn relaycast_ws_spawn_event_id_echo_still_uses_local_name_dedup() { + let value = json!({ + "type": "agent.spawn_requested", + "event_id": "evt_123", + "agent": { + "name": "worker-a", + "cli": "claude", + "task": "Ship it" + } + }); + + let control_key = relaycast_ws_control_dedup_key("ws_1", "agent.spawn_requested", &value) + .expect("control dedup key"); + let local_key = relaycast_spawn_control_dedup_key("ws_1", "worker-a"); + + assert_ne!(control_key, local_key); + assert!(relaycast_ws_should_apply_local_spawn_echo_dedup( + Some(control_key.as_str()), + &local_key + )); + + let now = Instant::now(); + let mut dedup = DedupCache::new(Duration::from_secs(60), 16); + assert!(dedup.insert_if_new(&local_key, now)); + assert!(dedup.insert_if_new(&control_key, now + Duration::from_secs(1))); + assert!(!dedup.insert_if_new(&local_key, now + Duration::from_secs(2))); + } + #[test] fn unknown_worker_error_message_matches_release_failures() { assert!(is_unknown_worker_error_message("unknown worker 'worker-a'"));