specrails-desktop 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/dist/assets/{ActivityFeedPage-BTYWMRwB.js → ActivityFeedPage-LKqd18-G.js} +1 -1
- package/client/dist/assets/{AgentsPage-BfOCeHHt.js → AgentsPage-Cb-b-6Ot.js} +1 -1
- package/client/dist/assets/AnalyticsPage-HVxQQ1wy.js +1 -0
- package/client/dist/assets/{BarChart-DlshJN3Z.js → BarChart-BOyHB0dw.js} +1 -1
- package/client/dist/assets/{CodePage-DJCjDG4I.js → CodePage-DnOnwKGB.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-CTqZ9mbB.js → DesktopAnalyticsPage-D2auU39x.js} +1 -1
- package/client/dist/assets/{DocsDialog-KiJOSRvX.js → DocsDialog-CTuDX3GK.js} +1 -1
- package/client/dist/assets/{DocsPage-B17CR54A.js → DocsPage-DRyMmu0Z.js} +1 -1
- package/client/dist/assets/{ExportDropdown-BAu6z3b6.js → ExportDropdown-DO-GGiMh.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-CCG64Q-6.js → IntegrationsPage-BhbO4jFT.js} +1 -1
- package/client/dist/assets/{JobDetailPage-BnGJSMiS.js → JobDetailPage-DJooEg1s.js} +1 -1
- package/client/dist/assets/{JobsPage-B-tn4CIf.js → JobsPage-BbaC-YOg.js} +1 -1
- package/client/dist/assets/{addspec-DeDOztDr.js → addspec-B-BKlvDj.js} +1 -1
- package/client/dist/assets/{addspec-v8j6A7CD.js → addspec-BErjOdNK.js} +1 -1
- package/client/dist/assets/{addspec-B1FTtI2a.js → addspec-CIGb34PS.js} +1 -1
- package/client/dist/assets/{addspec-GWm4ffKl.js → addspec-C_3NBarY.js} +1 -1
- package/client/dist/assets/{addspec-Dw-0Dg-4.js → addspec-DDvvnE6N.js} +1 -1
- package/client/dist/assets/{addspec-DpRgmfmx.js → addspec-RuL8Zd7w.js} +1 -1
- package/client/dist/assets/{addspec-rp496P_F.js → addspec-rmhOaH7N.js} +1 -1
- package/client/dist/assets/{addspec-BCT9vm_c.js → addspec-xjDbYZWL.js} +1 -1
- package/client/dist/assets/{dist-js-B16c3VyT.js → dist-js-CiIVMsx3.js} +1 -1
- package/client/dist/assets/{dist-js-P2FkJ6fA.js → dist-js-Xc2lRKp2.js} +1 -1
- package/client/dist/assets/{index-AfVF6BgE.js → index-DK214dak.js} +45 -45
- package/client/dist/assets/index-DgKfQFcf.css +2 -0
- package/client/dist/assets/{lib-rNNmltMb.js → lib-Bo5s6xpe.js} +1 -1
- package/client/dist/assets/{settings-D3LurcR5.js → settings-BI_cVCqN.js} +1 -1
- package/client/dist/assets/{settings-5tzo0Rn3.js → settings-BRaLLSVi.js} +1 -1
- package/client/dist/assets/{settings-BEWv3VEu.js → settings-BcqH0oea.js} +1 -1
- package/client/dist/assets/settings-C0-7Fpxg.js +1 -0
- package/client/dist/assets/{settings-BORg56um.js → settings-D6QMBlGQ.js} +1 -1
- package/client/dist/assets/{settings-DcqWIEM6.js → settings-GOBKOTGl.js} +1 -1
- package/client/dist/assets/{settings-BDAW3trC.js → settings-pT3MzfRu.js} +1 -1
- package/client/dist/assets/{settings-Dfz8QbZS.js → settings-u-16ISHt.js} +1 -1
- package/client/dist/assets/{setup-D3rNZA9A.js → setup-BIIkb-_K.js} +1 -1
- package/client/dist/assets/{setup-C1IA-9YS.js → setup-BeQxu9kD.js} +1 -1
- package/client/dist/assets/{setup-pjgmYHx6.js → setup-CPa6GnlI.js} +1 -1
- package/client/dist/assets/{setup-gzLG8T6F.js → setup-CZl4OEJx.js} +1 -1
- package/client/dist/assets/{setup-C0dzw8j4.js → setup-ChpodNfn.js} +1 -1
- package/client/dist/assets/{setup-WP6WOYQh.js → setup-D_fjJH6u.js} +1 -1
- package/client/dist/assets/{setup-UD2aanGs.js → setup-YzD8DX4O.js} +1 -1
- package/client/dist/assets/{setup-CpfjaNut.js → setup-fRpDozmq.js} +1 -1
- package/client/dist/assets/{useProjectCache-Cid_GxRM.js → useProjectCache-DVNypkmR.js} +1 -1
- package/client/dist/index.html +5 -5
- package/docs/adding-a-provider.md +107 -0
- package/docs/agy-cli-provider-study.md +179 -0
- package/docs/gemini-cli-provider-study.md +301 -0
- package/docs/gemini-core-support-evaluation.md +160 -0
- package/docs/gemini.md +106 -0
- package/docs/internals/api-reference.md +4 -7
- package/package.json +2 -2
- package/server/dist/chat-manager.js +1 -1
- package/server/dist/core-package.js +6 -1
- package/server/dist/desktop-router.js +27 -8
- package/server/dist/explore-cwd-manager.js +1 -1
- package/server/dist/mobile/index.js +5 -5
- package/server/dist/mobile/mobile-admin-router.js +28 -35
- package/server/dist/mobile/mobile-datachannel.js +228 -0
- package/server/dist/mobile/mobile-gateway.js +72 -98
- package/server/dist/mobile/mobile-router.js +4 -35
- package/server/dist/mobile/mobile-signal-reconnect.js +84 -0
- package/server/dist/mobile/mobile-types.js +5 -5
- package/server/dist/mobile/mobile-webrtc-peer.js +129 -0
- package/server/dist/mobile/mobile-webrtc.js +117 -0
- package/server/dist/pricing.js +13 -0
- package/server/dist/project-router-tickets.js +63 -18
- package/server/dist/providers/gemini-adapter.js +234 -0
- package/server/dist/providers/index.js +4 -1
- package/server/dist/setup-manager.js +13 -7
- package/server/dist/setup-prerequisites.js +4 -0
- package/server/dist/spec-models.js +17 -3
- package/server/dist/util/cli-prompt.js +17 -1
- package/client/dist/assets/AnalyticsPage-AbVXKh9v.js +0 -1
- package/client/dist/assets/index-NlH5BbXJ.css +0 -2
- package/client/dist/assets/settings-yMubjqYw.js +0 -1
- package/server/dist/mobile/mobile-mdns.js +0 -81
- package/server/dist/mobile/mobile-pairing.js +0 -179
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
> Documento de planificación. Generado 2026-06-17 mediante auditoría multi-agente del repo real `/Users/javi/repos/specrails-core` + verificación de fuentes primarias de gemini-cli. Complementa `docs/gemini-cli-provider-study.md` (desktop) — esta evalúa el trabajo en **specrails-core** para habilitar rails/implement en Gemini.
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Evaluación: ¿Hay que tocar specrails-core para Gemini CLI?
|
|
6
|
+
|
|
7
|
+
> **Veredicto en una línea:** Sí, **obligatoriamente hay que tocar core** para que `implement`/`batch-implement` (rails) funcionen en Gemini. El desktop por sí solo (PR-A/PR-B + `gemini-adapter.ts`) cubre **solo** spec/explore/quick. El pipeline architect→developer→reviewer **es expresable sin rediseño de fases** en el modelo plano de Gemini, pero **sin un target gemini en core no hay agentes ni comandos ni skills que ejecutar**, así que los rails en gemini hoy son inejecutables.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Respuesta directa: ¿hay que tocar core?
|
|
12
|
+
|
|
13
|
+
**Sí, para los rails. No, para spec/explore/quick.** El corte es exacto y limpio:
|
|
14
|
+
|
|
15
|
+
### Ya funciona HOY sin tocar core (solo desktop adapter)
|
|
16
|
+
El desktop tiene `server/providers/gemini-adapter.ts` registrado (`server/providers/index.ts:12,16`), con `projectDirName: '.gemini'`, `instructionsFilename: 'GEMINI.md'`, `nativeCostUsd: false` (gemini-adapter.ts:225-235). Esto cubre las superficies que **NO dependen de artefactos instalados en el repo**:
|
|
17
|
+
- **Explore Spec** — spawnea gemini desde `explore-cwd/` con un `CLAUDE.md`/system-prompt embebido por el desktop; no lee `.gemini/*`.
|
|
18
|
+
- **Quick spec** (`POST /tickets/generate-spec`) — turno suelto, system prompt inyectado por el desktop.
|
|
19
|
+
- **Sidebar chat** — idem.
|
|
20
|
+
|
|
21
|
+
Estas tres superficies inyectan su propio prompt y no resuelven slash-commands ni subagentes del proyecto. Por eso el adapter desktop basta.
|
|
22
|
+
|
|
23
|
+
### EXIGE core (sin esto, los rails en gemini no arrancan)
|
|
24
|
+
`implement` y `batch-implement` **no son prompts** que el desktop inyecte: son **artefactos instalados en el repo** que el orquestador resuelve nativamente. El desktop solo pasa el comando resuelto al binario (`queue-manager.ts` `buildArgs('rail-job', …)`), y el binario espera encontrar en disco:
|
|
25
|
+
1. **Comandos** `implement` / `batch-implement` (en formato gemini: `.gemini/commands/*.toml`).
|
|
26
|
+
2. **Agentes** `sr-architect`/`sr-developer`/`sr-reviewer` (en `.gemini/agents/*.md` con frontmatter).
|
|
27
|
+
3. **Skills/comandos OpenSpec** (`opsx:*`) instalados por `openspec init --tools gemini`.
|
|
28
|
+
4. **`GEMINI.md`** (instrucciones de proyecto) + settings.
|
|
29
|
+
|
|
30
|
+
**Core no emite NADA de esto para gemini** — verificado: `grep -rni gemini` sobre `src/`, `templates/`, `integration-contract.json` devuelve **0 matches**. El tipo `Provider` es un set cerrado `'claude' | 'codex'` que **rechaza** cualquier otro valor en `install-config.ts:96` (`unsupported provider '...'`). Por tanto, hoy un proyecto gemini no puede ni siquiera instalarse: el handshake desktop→core (desktop escribe `provider:` en `install-config.yaml`, `setup-manager.ts:906`; core valida en `install-config.ts:96` e `init.ts:86`) **falla en duro** con "unsupported provider 'gemini'".
|
|
31
|
+
|
|
32
|
+
**Conclusión inequívoca:** spec/explore/quick = solo desktop, ya hecho. Rails (implement/batch-implement + agentes + skills + OpenSpec) = **bloqueado en core**, requiere un target gemini.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Cómo emite core hoy por proveedor
|
|
37
|
+
|
|
38
|
+
El flujo real de instalación: `init.ts` → `provider-detect.derivedPaths(provider)` → `scaffold.scaffoldInstallation` → `installOpenSpecProject`.
|
|
39
|
+
|
|
40
|
+
### Detección/selección de proveedor (todo cerrado a 2 literales)
|
|
41
|
+
- **Tipo:** `Provider = 'claude' | 'codex'` declarado en DOS sitios que deben coincidir: `provider-detect.ts:15` e `install-config.ts:13`.
|
|
42
|
+
- **Detección:** `detectAvailability()` solo sondea claude/codex en PATH — verificado verbatim:
|
|
43
|
+
```ts
|
|
44
|
+
const [claude, codex] = await Promise.all([commandExists('claude'), commandExists('codex')])
|
|
45
|
+
return { claude, codex }
|
|
46
|
+
```
|
|
47
|
+
(`provider-detect.ts:33-36`).
|
|
48
|
+
- **Validación dura:** `install-config.ts:96` rechaza ≠ claude/codex; `init.ts:86` rechaza el flag `--provider`; `bin/tui-installer.mjs:114/118`.
|
|
49
|
+
|
|
50
|
+
### Dónde divergen claude vs codex
|
|
51
|
+
Toda la divergencia es **imperativa, `if (input.provider === 'codex')`**, no hay registry/adapter (contraste con el `ProviderAdapter` del desktop). Sitios clave en `scaffold.ts`:
|
|
52
|
+
- **Convención de paths** centralizada en `derivedPaths` — verificado verbatim: codex → `{ '.codex', 'AGENTS.md' }`, else → `{ '.claude', 'CLAUDE.md' }` (`provider-detect.ts:87-92`).
|
|
53
|
+
- Esqueleto de directorios (`scaffold.ts:174-184`): claude `.claude/commands/specrails` + `.claude/skills`; codex `.codex/skills/{enrich,doctor,rails}`.
|
|
54
|
+
- Colocación de comandos (`copyBundledCommands`, `scaffold.ts:278-311`): claude copia `.md` verbatim; codex porta cada comando a `.codex/skills/<name>/SKILL.md` vía `writeCodexSkillFromCommand` (rewrite `.claude/`→`.codex/`, `/specrails:x`→`$x`) salvo que exista override en `templates/codex-skills/<name>/`.
|
|
55
|
+
- Agentes (`scaffold.ts:493-648`): claude coloca `.claude/agents/sr-*.md` + memory dirs; codex **salta agentes** y usa rail-skills en `.codex/skills/rails/`.
|
|
56
|
+
- Skills (`placeSkills`, `scaffold.ts:762-830`): claude **genera** `sr-*` skills desde el cuerpo del comando (`SKILL_FROM_COMMAND`, `scaffold.ts:46-82` + `writeClaudeSkillFromCommand`); codex copia `templates/codex-skills/rails/`.
|
|
57
|
+
- Settings (`scaffold.ts:244-250`): codex-only `applyCodexSettings` escribe `.codex/config.toml` + `AGENTS.md` con bloque sentinel-managed (`renderInitialAgentsMd`, `scaffold.ts:666-747`). Claude **no** tiene settings-writer aquí (su `CLAUDE.md` no lo escribe el instalador, solo lo chequea `doctor.ts:91`).
|
|
58
|
+
|
|
59
|
+
### Cómo se instala OpenSpec (esto SÍ es ya per-provider)
|
|
60
|
+
`installOpenSpecProject(repoRoot, provider)` (llamado solo en `init.ts:128`, **no** en update) shellea — verificado verbatim:
|
|
61
|
+
```ts
|
|
62
|
+
args: ['--yes', '-p', `@fission-ai/openspec@${pinnedVersion}`, '--',
|
|
63
|
+
'openspec', 'init', '--tools', provider, repoRoot]
|
|
64
|
+
```
|
|
65
|
+
(`init.ts:204-218`, pin `1.3.1` desde `pinned-versions.json:3`). **Pasa `provider` DIRECTO a `--tools`.** Esto significa que la capa OpenSpec **no es el bloqueante**: `openspec init --tools gemini` es nativamente soportado por openspec 1.3.1 (genera `.gemini/commands/opsx` + `.gemini/skills/openspec-*` + el dir agnóstico `openspec/{specs,changes}`). En cuanto core propague el literal `gemini` a esta llamada (pasos 1+3 abajo), OpenSpec hace lo correcto **sin cambios de código**.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. El target gemini en core: qué emitir, transform vs autoría nueva, additividad
|
|
70
|
+
|
|
71
|
+
### Qué debe emitir
|
|
72
|
+
| Artefacto | Formato gemini | Origen |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `implement`, `batch-implement` (+ resto comandos) | `.gemini/commands/specrails/*.toml` — **solo `prompt` + `description`**, sin `tools`/`model` | **Transform** de `templates/commands/specrails/*.md` (reusar el cuerpo, re-envolver en TOML) |
|
|
75
|
+
| `sr-architect/developer/reviewer` (+ opcionales) | `.gemini/agents/sr-*.md` con **YAML frontmatter** que SÍ lleva `model` + `tools` | **Transform** de `templates/agents/sr-*.md` (markdown ya compatible; ajustar frontmatter + `MEMORY_PATH`→`.gemini/agent-memory/`) |
|
|
76
|
+
| `GEMINI.md` + settings | `GEMINI.md` con bloque sentinel + `.gemini/settings.json` (con `experimental.enableAgents`) | **Autoría nueva** — clon de `applyCodexSettings`/`renderInitialAgentsMd` (`scaffold.ts:666-747`) |
|
|
77
|
+
| OpenSpec (`opsx:*`) | `.gemini/commands/opsx/*` + `.gemini/skills/openspec-*` | **Sin cambios** — lo emite `openspec init --tools gemini` |
|
|
78
|
+
|
|
79
|
+
### Transform vs autoría — la asimetría es **decisiva** y la dicta el formato gemini
|
|
80
|
+
El veredicto verificado fija la regla: **commands TOML solo aceptan `prompt`+`description`** (sin `tools`/`model` por comando), pero **subagents `.md` SÍ aceptan `tools`+`model` en frontmatter**. Consecuencia de arquitectura para el scaffold:
|
|
81
|
+
|
|
82
|
+
> El **model-routing y el tool-gating** que hoy viven embebidos en `implement.md` (bloque ORCHESTRATOR_MODEL ~líneas 160-171 + overrides por agente) **deben migrar al frontmatter de los `.gemini/agents/sr-*.md`**, NO al comando TOML.
|
|
83
|
+
|
|
84
|
+
Mapeo correcto: orquestador → `.gemini/commands/specrails/{implement,batch-implement}.toml` (solo prompt+description); roles → `.gemini/agents/sr-*.md` (con `model:`, `tools:`). Esto **espeja el split de codex** (comando-vs-skill) pero con el primitivo de subagente nativo de gemini en vez de `spawn_agent`.
|
|
85
|
+
|
|
86
|
+
**Neto:** es una **mezcla** — cuerpos de comandos y agentes son **transforms mecánicos** de los templates claude existentes; el **emisor TOML** (`writeGeminiCommandFromCommand`, análogo a `writeCodexSkillFromCommand`), el **escritor `GEMINI.md`/settings**, y el **plumbing de tipo/detección** son **autoría nueva**. Lo más limpio: añadir `templates/gemini-skills/` (o `templates/gemini-*`) como dir de override para lo que el transform mecánico produzca mal, igual que `templates/codex-skills/`.
|
|
87
|
+
|
|
88
|
+
### Cómo se añade de forma aditiva sin romper claude/codex
|
|
89
|
+
Es aditivo en **semántica** (cada branch es `=== 'codex'` / else-claude; añadir un brazo `=== 'gemini'` deja los paths existentes byte-idénticos; el allow-list de validación solo se ensancha, nunca rechaza configs previas válidas), pero **NO aditivo en código** (no hay abstracción → hay que editar cada sitio). Cambios concretos (~7 archivos + nuevo árbol de templates):
|
|
90
|
+
1. Ensanchar `Provider` en `provider-detect.ts:15` **y** `install-config.ts:13` (deben quedar idénticos); relajar validadores `init.ts:86`, `install-config.ts:96`, `bin/tui-installer.mjs:114/118`.
|
|
91
|
+
2. `detectAvailability` (`provider-detect.ts:33-36`): añadir `commandExists('gemini')`; extender `resolveProvider` (61-80) + el mensaje de error que hoy solo nombra Claude+Codex.
|
|
92
|
+
3. `derivedPaths` (`provider-detect.ts:87-92`): brazo gemini → `{ '.gemini', 'GEMINI.md' }`.
|
|
93
|
+
4. `scaffold.ts`: brazos gemini en cada `=== 'codex'` (dir skeleton 174-184, colocación comandos 278-325, quick-tier 493-648, `placeSkills` 762-830, prune 454-462) + `applyGeminiSettings` clon de `applyCodexSettings`.
|
|
94
|
+
5. `prereqs.ts:91-104`: gatear el bloque claude-only o dar a gemini un path mínimo (sin auth-assert, como codex).
|
|
95
|
+
6. `doctor.ts:96/160/167` + `update.ts:196-210` (`resolveExistingProvider` probe `.gemini`).
|
|
96
|
+
7. `integration-contract.json:3-26`: bloque `providers.gemini` (la superficie casi-declarativa que lee el desktop; la de menor fricción).
|
|
97
|
+
8. OpenSpec: **cero cambios de código** más allá de propagar `gemini` (pasos 1+3 → ya llega a `init.ts:215`).
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 4. El bloqueante de orquestación: ¿plano de gemini lo soporta?
|
|
102
|
+
|
|
103
|
+
**Veredicto honesto (verificado contra fuentes Google primarias): el pipeline ES expresable en el modelo plano de gemini SIN rediseño de fases.** No es un blocker de capacidad; es trabajo imperativo en core.
|
|
104
|
+
|
|
105
|
+
Razonamiento (todo confirmado):
|
|
106
|
+
- El pipeline es **recursivo pero SHALLOW**: el orquestador spawnea architect→developer→reviewer a **profundidad 1**; las hojas **nunca re-spawnean** (un developer no llama a un reviewer; lo único "hacia abajo" es un `Skill("opsx:ff/apply/archive")` **in-context**, que shellea al CLI `openspec`, **no es un subagente**). Contrato verbatim: `implement.md:5` "delegate to the agents"; `implement.md:547` spawn de sr-architect; codex `implement/SKILL.md:24` "Each phase MUST be a real spawn_agent call".
|
|
107
|
+
- El modelo de gemini es exactamente eso: orquestador delega depth-1, subagentes **FLAT** (`docs/core/subagents.md` verbatim: "subagents cannot call other subagents"). La restricción plana (las hojas no spawnean) **nunca se viola** porque todo el spawning requerido ocurre en el orquestador a depth-1.
|
|
108
|
+
- **El único rediseño** es `batch-implement`, que conceptualmente anida (batch→implement→roles = depth 2), ilegal en plano. **Pero el proyecto YA lo resolvió para codex**: `codex-skills/batch-implement/SKILL.md:5` "Drives architect/developer/reviewer spawns at the ROOT agent level — does NOT spawn a nested $implement sub-agent per ticket". El batch-implement gemini **reutiliza esa misma disciplina de aplanamiento ya probada**.
|
|
109
|
+
|
|
110
|
+
**Por tanto: NO hay rediseño del pipeline de fases. La viabilidad de rails en gemini está gateada por el trabajo imperativo en core, no por un gap de capacidad de gemini.**
|
|
111
|
+
|
|
112
|
+
Matiz honesto (no inventado): subagents en gemini son **experimentales** (namespace `experimental.enableAgents`, PR #14371, públicos desde v0.38.1 — ahora habilitados por defecto pero aún bajo `experimental.*`). Y queda un **unknown externo** que estos repos no pueden resolver: si el orquestador gemini puede **iniciar** spawns en headless/non-interactivo o si la delegación `@name` está garantizada sin TTY (ver riesgos §6).
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 5. Esfuerzo + secuenciación + versión + reutilización agy/Antigravity
|
|
117
|
+
|
|
118
|
+
**Tamaño del trabajo en core:** moderado, no trivial. ~7 archivos editados (`provider-detect`, `install-config`, `scaffold`, `prereqs`, `doctor`, `init`, `update`, `bin/tui-installer.mjs`) + `integration-contract.json` + **un árbol nuevo `templates/gemini-skills/`** (o `gemini-*`) + tests (incluido el contrato `reserved-paths.test.ts`, que debe seguir verde para init+update gemini). La falta de abstracción installer-side significa **N ediciones inline, no un descriptor**. La mayor parte del esfuerzo de autoría es: el emisor TOML, el escritor `GEMINI.md`/settings, y **validar empíricamente** que la orquestación plana de gemini ejecuta las fases en headless.
|
|
119
|
+
|
|
120
|
+
**Secuenciación con el beta desktop:**
|
|
121
|
+
1. **PR-A #396** (generaliza tipos) y **PR-B #397** (adapter beta-gated) habilitan spec/explore/quick en gemini — **independientes de core**, se mergean primero.
|
|
122
|
+
2. El target gemini en core es **secuencialmente posterior** y desbloquea rails. Mientras tanto, el desktop debería **ocultar rails/implement para gemini** (capability-intersection: igual que codex fuerza `profile=null` y oculta Agents/Integrations) hasta que core publique el target.
|
|
123
|
+
|
|
124
|
+
**Versión de core:** feature aditiva → conventional-commit `feat:` → release-please **MINOR**. Desde 4.7.1 actual → **4.8.0**. El profile-gate `>= 4.1.0` (`queue-manager.ts:77`, `profiles-router.ts:233`) **no se toca**: proyectos gemini en 4.8.0 ya lo satisfacen. Que gemini **participe** en profiles es una decisión de política del desktop (probablemente como codex: `profile=null`), no un cambio de gate.
|
|
125
|
+
|
|
126
|
+
**¿Puramente aditivo?** En contrato/runtime: **sí** (no rompe claude/codex). En código: **no** (cada branch imperativo gana un brazo gemini). Reserved-paths sin cambios.
|
|
127
|
+
|
|
128
|
+
**Reutilización agy/Antigravity (no es trabajo tirado):** Antigravity comparte el harness `~/.gemini` (mismas convenciones `.gemini/commands/*.toml`, `.gemini/agents/*.md`, `GEMINI.md`). Un target gemini en core que emita en formato `.gemini/*` **sirve de base directa para agy** más adelante — el scaffold gemini es reutilizable casi tal cual. Esto sube el ROI del trabajo de core.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 6. Recomendación
|
|
133
|
+
|
|
134
|
+
### Decisión: **HACER, pero DESPUÉS del beta desktop de spec/explore/quick (no en paralelo, no bloqueante del beta).**
|
|
135
|
+
|
|
136
|
+
Justificación: el beta desktop (PR-A/PR-B) entrega valor real de gemini (spec/explore/quick) **sin tocar core** y sin riesgo. Los rails en gemini son un trabajo de core mayor, con un unknown empírico (headless), y **no deben retrasar el beta**. El desktop oculta rails para gemini mientras tanto.
|
|
137
|
+
|
|
138
|
+
### Ownership
|
|
139
|
+
- **Desktop owna:** el adapter gemini (ya hecho), capability-intersection para ocultar rails/Agents/Integrations en gemini hasta que core publique, command-syntax translation en `queue-manager` (hoy claude=verbatim, codex=`/x`→`$x`; gemini necesita su forma), y `profile=null` para rails gemini.
|
|
140
|
+
- **Core owna:** el target de instalación gemini completo (tipos/detección, `.gemini/commands/*.toml`, `.gemini/agents/sr-*.md`, `GEMINI.md`/settings, `templates/gemini-skills/`, `integration-contract.json` gemini block, propagar `gemini` a `openspec init --tools`), y los tests.
|
|
141
|
+
|
|
142
|
+
### Primer paso concreto en core
|
|
143
|
+
Un **spike de validación de orquestación**, ANTES de escribir el scaffold completo: instalar manualmente a mano un `.gemini/commands/specrails/implement.toml` (prompt mínimo) + `.gemini/agents/sr-{architect,developer,reviewer}.md` (frontmatter con `model`/`tools`) + `enableAgents`, correr `openspec init --tools gemini`, y ejecutar el binario `gemini` en modo headless/exec (como lo spawnearía el desktop) para **confirmar empíricamente** que el orquestador inicia los spawns depth-1 y completa architect→developer→reviewer sin TTY. Si pasa, se procede al scaffold; si no, el riesgo se materializa antes de invertir en los 7 archivos.
|
|
144
|
+
|
|
145
|
+
### Riesgos honestos
|
|
146
|
+
1. **TOML sin `tools`/`model` por comando** — confirmado: los `.gemini/commands/*.toml` no pueden restringir tools ni fijar modelo. El routing/gating **debe** vivir en el frontmatter de los `.gemini/agents/*.md`. Si algún comando dependía de gating a nivel comando, hay que re-arquitecturarlo a nivel agente.
|
|
147
|
+
2. **Subagentes experimentales** — `experimental.enableAgents`; comportamiento puede cambiar entre versiones de gemini-cli. Pinear/probar contra una versión concreta.
|
|
148
|
+
3. **Shell-injection con confirm interactivo en headless** — los agentes shellean al CLI `openspec` vía Skill in-context; si gemini exige confirmación interactiva de tool-calls de shell y el desktop lo spawnea sin TTY, las fases podrían colgarse. **Riesgo no verificado, alto impacto** — es lo que el spike del primer paso debe descartar.
|
|
149
|
+
4. **`@`-routing headless no garantizado** — la delegación forzada `@sr-architect` puede no funcionar sin interactividad; quizás haya que apoyarse en auto-delegación, menos determinista para un pipeline que exige las tres fases.
|
|
150
|
+
5. **OpenSpec install adaptado a gemini** — `openspec init --tools gemini` está confirmado nativo en 1.3.1, pero **no verifiqué empíricamente en esta sesión** que los `.gemini/skills/openspec-*` que genera sean **invocables** por el orquestador gemini en el flujo real (solo que el `--tools` lo acepta). Validar en el spike.
|
|
151
|
+
|
|
152
|
+
### Lo que NO está cierto (declarado explícitamente)
|
|
153
|
+
- Si el orquestador gemini puede **iniciar** spawns en headless/exec, o si solo el harness puede pre-definir un set fijo de agentes. Si fuera lo segundo, el orquestador `implement` habría que re-expresarlo como agentes paralelos definidos por harness en vez de spawns iniciados por el orquestador — **lift mayor**. Esto **decide la viabilidad real** y no es deducible de estos repos.
|
|
154
|
+
- El comportamiento exacto de confirm de shell-calls de gemini en headless (riesgo #3).
|
|
155
|
+
|
|
156
|
+
**Archivos clave (absolutos):**
|
|
157
|
+
- Core gap: `/Users/javi/repos/specrails-core/src/installer/phases/provider-detect.ts` (15, 33-36, 87-92), `/Users/javi/repos/specrails-core/src/installer/phases/install-config.ts` (13, 96), `/Users/javi/repos/specrails-core/src/installer/phases/scaffold.ts` (174-184, 244-250, 278-311, 666-747, 762-830), `/Users/javi/repos/specrails-core/src/installer/commands/init.ts` (86, 128, 204-218), `/Users/javi/repos/specrails-core/integration-contract.json` (3-26), `/Users/javi/repos/specrails-core/pinned-versions.json` (3).
|
|
158
|
+
- Templates a transformar: `/Users/javi/repos/specrails-core/templates/commands/specrails/implement.md`, `/Users/javi/repos/specrails-core/templates/commands/specrails/batch-implement.md`, `/Users/javi/repos/specrails-core/templates/agents/sr-{architect,developer,reviewer}.md`.
|
|
159
|
+
- Precedente de aplanamiento codex: `/Users/javi/repos/specrails-core/templates/codex-skills/batch-implement/SKILL.md` (5, 19-32), `/Users/javi/repos/specrails-core/templates/codex-skills/implement/SKILL.md` (24).
|
|
160
|
+
- Desktop (ya hecho): `/Users/javi/repos/specrails-desktop/server/providers/gemini-adapter.ts` (225-235), `/Users/javi/repos/specrails-desktop/server/providers/index.ts` (12, 16); a tocar: `/Users/javi/repos/specrails-desktop/server/queue-manager.ts` (command translation, `profile=null`), `/Users/javi/repos/specrails-desktop/client/src/lib/provider-capabilities.ts` (ocultar rails gemini).
|
package/docs/gemini.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Using Specrails with the Gemini CLI
|
|
2
|
+
|
|
3
|
+
Specrails supports Google's [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
|
4
|
+
as a third AI provider alongside Claude Code and the Codex CLI.
|
|
5
|
+
|
|
6
|
+
> **Beta — opt-in.** Gemini is gated behind `SPECRAILS_GEMINI_BETA` and is **off by
|
|
7
|
+
> default**. Until you set it, Gemini is invisible everywhere (it won't show in Add
|
|
8
|
+
> Project and can't be selected). See [Enabling the beta](#enabling-the-beta).
|
|
9
|
+
>
|
|
10
|
+
> **Scope today.** With the beta on, Gemini powers the *non-pipeline* surfaces —
|
|
11
|
+
> Explore Spec, Quick spec, AI Edit, the terminal launcher, and cost analytics.
|
|
12
|
+
> The full **rails pipeline** (`/specrails:implement`, `batch-implement`) needs a
|
|
13
|
+
> Gemini artifact target in `specrails-core` (`.gemini/commands/*.toml` +
|
|
14
|
+
> `.gemini/agents/sr-*.md`), which ships separately. Rails are hidden for Gemini
|
|
15
|
+
> projects until then.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- **Gemini CLI ≥ 0.11.0** on your `PATH` (`npm i -g @google/gemini-cli`; `gemini --version`).
|
|
20
|
+
Older versions lack `--output-format stream-json` and headless `--resume`.
|
|
21
|
+
- **A Gemini API key.** Specrails spawns Gemini headlessly, so it needs
|
|
22
|
+
non-interactive auth: set `GEMINI_API_KEY` (a paid Gemini Developer API key from
|
|
23
|
+
[Google AI Studio](https://aistudio.google.com/apikey)). The free OAuth
|
|
24
|
+
"Login with Google" tier exists but is being wound down for the CLI and is not a
|
|
25
|
+
reliable unattended path — use an API key.
|
|
26
|
+
|
|
27
|
+
## Enabling the beta
|
|
28
|
+
|
|
29
|
+
Set the env var where the Specrails server runs, then restart the app:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
SPECRAILS_GEMINI_BETA=1 # or 'true'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
With it set, `GET /api/available-providers` reports Gemini's real install status,
|
|
36
|
+
Add Project shows a **Gemini** checkbox, and projects can select it.
|
|
37
|
+
|
|
38
|
+
## Adding a Gemini project
|
|
39
|
+
|
|
40
|
+
Add Project → tick **Gemini** (visible once the beta is on and `gemini` is on PATH).
|
|
41
|
+
A project can install Gemini alongside Claude/Codex; the first provider selected is
|
|
42
|
+
the primary/default, and per-invocation engine pickers let you choose per spec/rail.
|
|
43
|
+
|
|
44
|
+
## What's different vs Claude
|
|
45
|
+
|
|
46
|
+
- **Cost is estimated, not native.** Gemini does not report a USD cost in its
|
|
47
|
+
stream, so Specrails estimates it from a rate card (`server/pricing.ts`,
|
|
48
|
+
keys `gemini:<model>`). Token counts (incl. cached) come straight from the run.
|
|
49
|
+
- **No per-agent profiles / SMASH / Contract Refine.** These are Claude-only; on a
|
|
50
|
+
mixed project the capability intersection hides them (same as Codex).
|
|
51
|
+
- **System prompt is folded.** Gemini has no `--system-prompt` flag, so for
|
|
52
|
+
non-Explore actions the system prompt is folded into the user prompt. Explore
|
|
53
|
+
turns stay user-only and trust the app-managed `GEMINI.md` in the explore cwd.
|
|
54
|
+
- **OTEL is native.** Unlike Codex, Gemini emits OTLP via `GEMINI_TELEMETRY_*`, so
|
|
55
|
+
pipeline telemetry needs no synthetic bridge.
|
|
56
|
+
|
|
57
|
+
## Models
|
|
58
|
+
|
|
59
|
+
Curated catalog (`server/providers/gemini-adapter.ts`): `gemini-2.5-pro` (default),
|
|
60
|
+
`gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-3.1-pro-preview`. Concrete GA
|
|
61
|
+
ids are pinned (preview ids rotate). Free-tier API keys serve Flash; Pro/preview
|
|
62
|
+
need billing.
|
|
63
|
+
|
|
64
|
+
## Trusted folders (headless)
|
|
65
|
+
|
|
66
|
+
Gemini's "trusted folders" gate silently overrides `--yolo` back to `default` —
|
|
67
|
+
blocking *every* tool call — in a directory it doesn't trust. Specrails injects
|
|
68
|
+
`GEMINI_CLI_TRUST_WORKSPACE=true` into every Gemini spawn (`server/util/cli-prompt.ts`
|
|
69
|
+
`spawnGemini`) so headless rails/explore can actually run tools. No action needed
|
|
70
|
+
from you.
|
|
71
|
+
|
|
72
|
+
## Troubleshooting
|
|
73
|
+
|
|
74
|
+
- **"Gemini" never appears in Add Project** → the beta is off (`SPECRAILS_GEMINI_BETA`
|
|
75
|
+
unset), or `gemini` isn't on PATH. Check `GET /api/available-providers`.
|
|
76
|
+
- **Tools never run / job does nothing** → almost always auth or trust. Confirm
|
|
77
|
+
`GEMINI_API_KEY` is set in the server env; the trust var is handled automatically.
|
|
78
|
+
- **`limit: 0` / quota errors on Pro** → free-tier API keys don't serve Pro models;
|
|
79
|
+
use a Flash model or enable billing.
|
|
80
|
+
- **Cost shows `—`** → no `gemini:<model>` pricing row for the model used; add one to
|
|
81
|
+
`server/pricing.ts`.
|
|
82
|
+
|
|
83
|
+
## Emergency rollback
|
|
84
|
+
|
|
85
|
+
Unset `SPECRAILS_GEMINI_BETA` (or set it to `0`). Gemini disappears from the UI and
|
|
86
|
+
becomes unselectable immediately; existing non-Gemini projects are unaffected. The
|
|
87
|
+
adapter stays registered but dormant.
|
|
88
|
+
|
|
89
|
+
## Architecture pointers (for specrails-desktop developers)
|
|
90
|
+
|
|
91
|
+
- Adapter: `server/providers/gemini-adapter.ts` (`nativeCostUsd:false`,
|
|
92
|
+
`nativeOtelEnv:true`, `systemPromptArg:false`, `instructionsFilename:'GEMINI.md'`,
|
|
93
|
+
`mcpRegistration:'project-json'`). Registered in `server/providers/index.ts`.
|
|
94
|
+
- Stream schema (validated against gemini 0.46): `init{session_id,model}` /
|
|
95
|
+
`message{role,content,delta}` / `tool_use{tool_name,tool_id,parameters}` /
|
|
96
|
+
`tool_result` / `result{stats}`. `stats.input_tokens` includes `stats.cached`.
|
|
97
|
+
- Beta gate + provider list: `server/desktop-router.ts` (`isGeminiBetaEnabled`,
|
|
98
|
+
`/available-providers`, `POST /projects`).
|
|
99
|
+
- Trust-folder env: `server/util/cli-prompt.ts` `spawnGemini`.
|
|
100
|
+
|
|
101
|
+
## See also
|
|
102
|
+
|
|
103
|
+
- [`docs/codex.md`](./codex.md) — the Codex provider (the other non-native provider).
|
|
104
|
+
- [`docs/adding-a-provider.md`](./adding-a-provider.md) — how to add a provider.
|
|
105
|
+
- [`docs/gemini-cli-provider-study.md`](./gemini-cli-provider-study.md) — the design study.
|
|
106
|
+
- [`docs/gemini-core-support-evaluation.md`](./gemini-core-support-evaluation.md) — the rails/core work.
|
|
@@ -94,18 +94,15 @@ A non-localhost `Origin` header is rejected by the CORS middleware with `403 For
|
|
|
94
94
|
|
|
95
95
|
### Mobile Gateway admin (`/api/mobile/*`)
|
|
96
96
|
|
|
97
|
-
Loopback-only admin surface for the Mobile Gateway (the gateway itself is a separate HTTPS
|
|
97
|
+
Loopback-only admin surface for the Mobile Gateway (the gateway itself is a separate HTTPS listener that tunnels the web companion's RPC over WebRTC). The wire contract the phone consumes intentionally keeps the legacy `hub.*` names — see the frozen mobile wire-compat note in `CLAUDE.md`.
|
|
98
98
|
|
|
99
99
|
| Method | Path | Notes |
|
|
100
100
|
|--------|------|-------|
|
|
101
|
-
| `GET` | `/status` | Gateway status (`{ enabled, running, port, certFingerprint,
|
|
101
|
+
| `GET` | `/status` | Gateway status (`{ enabled, running, port, certFingerprint, desktopName }`) |
|
|
102
102
|
| `POST` | `/enable` | Start the gateway (generates the TLS identity on first run) |
|
|
103
103
|
| `POST` | `/disable` | Stop the gateway |
|
|
104
|
-
| `POST` | `/
|
|
105
|
-
| `
|
|
106
|
-
| `POST` | `/pairing-session/approve` | Approve the pending claim (pairs the device) |
|
|
107
|
-
| `POST` | `/pairing-session/deny` | Deny the pending claim |
|
|
108
|
-
| `DELETE` | `/pairing-session` | Cancel the pairing session |
|
|
104
|
+
| `POST` | `/webrtc/offer` | Create a serverless pairing offer (offer SDP + single-use secret + desktop identity) for the first QR |
|
|
105
|
+
| `POST` | `/webrtc/answer` | Apply the companion's scanned answer SDP to the open offer |
|
|
109
106
|
| `GET` | `/devices` | List paired devices |
|
|
110
107
|
| `DELETE` | `/devices/:id` | Revoke a paired device |
|
|
111
108
|
| `POST` | `/cert/rotate` | Rotate the gateway TLS identity (unpairs every device) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specrails-desktop",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
"ci": "npm run typecheck && npm run check-core-compat && npm run test:coverage && cd client && npm run test:coverage"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@homebridge/ciao": "^1.3.9",
|
|
46
45
|
"@tailwindcss/typography": "^0.5.19",
|
|
47
46
|
"ajv": "^8.17.1",
|
|
48
47
|
"better-sqlite3": "^12.8.0",
|
|
@@ -57,6 +56,7 @@
|
|
|
57
56
|
"read-excel-file": "^9.0.6",
|
|
58
57
|
"selfsigned": "^5.5.0",
|
|
59
58
|
"tree-kill": "^1.2.2",
|
|
59
|
+
"werift": "^0.23.0",
|
|
60
60
|
"ws": "^8.16.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
@@ -278,7 +278,7 @@ class ChatManager {
|
|
|
278
278
|
slug: this._projectSlug,
|
|
279
279
|
projectPath: this._cwd,
|
|
280
280
|
projectName: this._projectName,
|
|
281
|
-
provider:
|
|
281
|
+
provider: providerId ?? this._adapter.id,
|
|
282
282
|
});
|
|
283
283
|
console.log(`[chat-manager] explore spawn cwd=${cwd} (mcp=off)`);
|
|
284
284
|
return cwd;
|
|
@@ -8,7 +8,12 @@ exports.CORE_PACKAGE_SPEC = void 0;
|
|
|
8
8
|
* minute it is published — adopting a new major is a deliberate one-line
|
|
9
9
|
* bump here, shipped through the app's own release pipeline.
|
|
10
10
|
*
|
|
11
|
+
* Floor bumped to 4.8.0 — the version that ships the Gemini provider target
|
|
12
|
+
* (`.gemini/` commands + agents). It also changes the `npx` package-spec string,
|
|
13
|
+
* which invalidates any stale `_npx` exec cache that npx would otherwise reuse
|
|
14
|
+
* for the old `^4.6.0` range (so users actually resolve the newer core).
|
|
15
|
+
*
|
|
11
16
|
* `SPECRAILS_CORE_BIN` remains the escape hatch for local/linked builds
|
|
12
17
|
* (see getCoreCommand in setup-manager.ts).
|
|
13
18
|
*/
|
|
14
|
-
exports.CORE_PACKAGE_SPEC = 'specrails-core@^4.
|
|
19
|
+
exports.CORE_PACKAGE_SPEC = 'specrails-core@^4.8.0';
|
|
@@ -29,6 +29,14 @@ function isCodexBetaDisabled() {
|
|
|
29
29
|
const v = process.env.SPECRAILS_CODEX_BETA ?? process.env.SPECRAILS_HUB_CODEX_BETA;
|
|
30
30
|
return v === '0';
|
|
31
31
|
}
|
|
32
|
+
// Gemini is opt-IN (default off): unlike codex, its stream-json schema has not
|
|
33
|
+
// yet been validated against a live binary, so it stays hidden + unselectable
|
|
34
|
+
// until SPECRAILS_GEMINI_BETA=1 (or 'true'). The adapter is always registered
|
|
35
|
+
// (pricing/getAdapter work); only project selection is gated.
|
|
36
|
+
function isGeminiBetaEnabled() {
|
|
37
|
+
const v = (process.env.SPECRAILS_GEMINI_BETA ?? '').toLowerCase();
|
|
38
|
+
return v === '1' || v === 'true';
|
|
39
|
+
}
|
|
32
40
|
// Theme allow-list. Mirror of THEME_IDS in `client/src/lib/themes.ts` —
|
|
33
41
|
// kept duplicated to avoid pulling client code into the server bundle.
|
|
34
42
|
const THEME_ID_ALLOWLIST = new Set(['dracula', 'aurora-light', 'obsidian-dark', 'matrix', 'specrails']);
|
|
@@ -144,14 +152,19 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
144
152
|
const providers = (0, core_compat_1.detectAvailableCLIs)();
|
|
145
153
|
// tiers: quick install is always available (app-driven config); full requires an AI CLI
|
|
146
154
|
const tiers = ['quick'];
|
|
147
|
-
if (
|
|
155
|
+
if (Object.values(providers).some(Boolean))
|
|
148
156
|
tiers.push('full');
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
// Return the full detected map (registry-driven) so a newly-registered
|
|
158
|
+
// provider surfaces here with no edit. Apply per-provider beta gates: codex
|
|
159
|
+
// is forced unavailable when SPECRAILS_CODEX_BETA=0 (emergency rollback).
|
|
160
|
+
const gated = { ...providers };
|
|
161
|
+
if (isCodexBetaDisabled())
|
|
162
|
+
gated.codex = false;
|
|
163
|
+
// Gemini is opt-in: omit it entirely (not just `false`) when the beta flag is
|
|
164
|
+
// off, so it stays fully invisible in the UI until SPECRAILS_GEMINI_BETA=1.
|
|
165
|
+
if (!isGeminiBetaEnabled())
|
|
166
|
+
delete gated.gemini;
|
|
167
|
+
res.json({ ...gated, tiers });
|
|
155
168
|
});
|
|
156
169
|
router.get('/setup-prerequisites', (req, res) => {
|
|
157
170
|
const status = (0, setup_prerequisites_1.getSetupPrerequisitesStatus)();
|
|
@@ -218,6 +231,12 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
218
231
|
});
|
|
219
232
|
return;
|
|
220
233
|
}
|
|
234
|
+
if (providers.includes('gemini') && !isGeminiBetaEnabled()) {
|
|
235
|
+
res.status(400).json({
|
|
236
|
+
error: 'Gemini provider is in beta and disabled by default. Set SPECRAILS_GEMINI_BETA=1 to enable.',
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
221
240
|
const resolvedPath = path_1.default.resolve(projectPath);
|
|
222
241
|
// Validate path exists
|
|
223
242
|
if (!fs_1.default.existsSync(resolvedPath)) {
|
|
@@ -243,7 +262,7 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
243
262
|
name: derivedName,
|
|
244
263
|
path: canonicalPath,
|
|
245
264
|
provider: providers[0],
|
|
246
|
-
providers
|
|
265
|
+
providers,
|
|
247
266
|
});
|
|
248
267
|
broadcast({
|
|
249
268
|
type: 'desktop.project_added',
|
|
@@ -127,7 +127,7 @@ function ensureExploreCwd(input, baseDir) {
|
|
|
127
127
|
// — provider is immutable post-creation but the lifecycle code MUST handle
|
|
128
128
|
// the edge per spec), remove any stale instructions file authored by a
|
|
129
129
|
// different provider so the explore-cwd doesn't carry both.
|
|
130
|
-
const STALE_INSTRUCTION_FILES = ['CLAUDE.md', 'AGENTS.md'];
|
|
130
|
+
const STALE_INSTRUCTION_FILES = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
|
|
131
131
|
for (const stale of STALE_INSTRUCTION_FILES) {
|
|
132
132
|
if (stale === adapter.instructionsFilename)
|
|
133
133
|
continue;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Mobile Gateway — public surface. The gateway is a second HTTPS
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// desktop API over per-device tokens. The main server at 127.0.0.1:4200 is
|
|
6
|
-
// exposed.
|
|
2
|
+
// Mobile Gateway — public surface. The gateway is a second HTTPS listener in the
|
|
3
|
+
// same Node process (default :4202, OFF by default) that pairs the web companion
|
|
4
|
+
// serverlessly over WebRTC and exposes a redacted, deny-by-default allow-list of
|
|
5
|
+
// the desktop API over per-device tokens. The main server at 127.0.0.1:4200 is
|
|
6
|
+
// never exposed.
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.MOBILE_ALLOWLIST = exports.getMobileEventBus = exports.createMobileAdminRouter = exports.MobileGateway = void 0;
|
|
9
9
|
var mobile_gateway_1 = require("./mobile-gateway");
|
|
@@ -24,41 +24,6 @@ function createMobileAdminRouter(deps) {
|
|
|
24
24
|
await gateway.setEnabled(false);
|
|
25
25
|
res.json(gateway.status());
|
|
26
26
|
});
|
|
27
|
-
// —— Pairing session ——
|
|
28
|
-
router.post('/pairing-session', (_req, res) => {
|
|
29
|
-
if (!gateway.running || !gateway.pairing) {
|
|
30
|
-
res.status(409).json({ error: 'Enable mobile access first' });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
res.json({ qr: gateway.pairing.createSession() });
|
|
34
|
-
});
|
|
35
|
-
router.get('/pairing-session', (_req, res) => {
|
|
36
|
-
if (!gateway.pairing) {
|
|
37
|
-
res.json({ status: 'none' });
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
res.json(gateway.pairing.getDesktopState() ?? { status: 'none' });
|
|
41
|
-
});
|
|
42
|
-
router.post('/pairing-session/approve', (_req, res) => {
|
|
43
|
-
if (!gateway.pairing) {
|
|
44
|
-
res.status(409).json({ error: 'No pairing session' });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const result = gateway.pairing.approve();
|
|
48
|
-
if (!result.ok) {
|
|
49
|
-
res.status(409).json({ error: result.reason });
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
res.json({ ok: true });
|
|
53
|
-
});
|
|
54
|
-
router.post('/pairing-session/deny', (_req, res) => {
|
|
55
|
-
gateway.pairing?.deny();
|
|
56
|
-
res.json({ ok: true });
|
|
57
|
-
});
|
|
58
|
-
router.delete('/pairing-session', (_req, res) => {
|
|
59
|
-
gateway.pairing?.cancel();
|
|
60
|
-
res.json({ ok: true });
|
|
61
|
-
});
|
|
62
27
|
// —— Devices ——
|
|
63
28
|
router.get('/devices', (_req, res) => {
|
|
64
29
|
res.json({ devices: (0, mobile_devices_1.listDevices)(desktopDb) });
|
|
@@ -80,5 +45,33 @@ function createMobileAdminRouter(deps) {
|
|
|
80
45
|
const status = await gateway.rotateCert();
|
|
81
46
|
res.json(status);
|
|
82
47
|
});
|
|
48
|
+
// —— Serverless WebRTC pairing signaling (webview ↔ local peer) ——
|
|
49
|
+
// The webview asks for an offer (→ first QR), then posts the companion's
|
|
50
|
+
// scanned answer SDP. No SDP ever leaves the desktop except as a QR.
|
|
51
|
+
router.post('/webrtc/offer', async (_req, res) => {
|
|
52
|
+
if (!gateway.running || !gateway.webrtc) {
|
|
53
|
+
res.status(409).json({ error: 'Enable mobile access first' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const offer = await gateway.webrtcOffer();
|
|
58
|
+
if (!offer) {
|
|
59
|
+
res.status(409).json({ error: 'WebRTC unavailable' });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.json(offer);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
res.status(500).json({ error: 'Failed to create offer' });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
router.post('/webrtc/answer', async (req, res) => {
|
|
69
|
+
const sdp = typeof req.body?.sdp === 'string' ? req.body.sdp : '';
|
|
70
|
+
if (!sdp) {
|
|
71
|
+
res.status(400).json({ error: 'sdp required' });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
res.json({ ok: await gateway.webrtcAnswer(sdp) });
|
|
75
|
+
});
|
|
83
76
|
return router;
|
|
84
77
|
}
|