opencode-flema-engram-sidebar 0.1.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/LICENSE +21 -0
- package/README.md +472 -0
- package/dist/adapters/cloud.d.ts +18 -0
- package/dist/adapters/cloud.d.ts.map +1 -0
- package/dist/adapters/cloud.js +53 -0
- package/dist/adapters/cloud.js.map +1 -0
- package/dist/adapters/composite.d.ts +20 -0
- package/dist/adapters/composite.d.ts.map +1 -0
- package/dist/adapters/composite.js +83 -0
- package/dist/adapters/composite.js.map +1 -0
- package/dist/adapters/local.d.ts +15 -0
- package/dist/adapters/local.d.ts.map +1 -0
- package/dist/adapters/local.js +178 -0
- package/dist/adapters/local.js.map +1 -0
- package/dist/adapters/types.d.ts +68 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/errors.d.ts +5 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +22 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/resources/engram-change-artifacts.d.ts +11 -0
- package/dist/mcp/resources/engram-change-artifacts.d.ts.map +1 -0
- package/dist/mcp/resources/engram-change-artifacts.js +24 -0
- package/dist/mcp/resources/engram-change-artifacts.js.map +1 -0
- package/dist/mcp/resources/engram-change-state.d.ts +11 -0
- package/dist/mcp/resources/engram-change-state.d.ts.map +1 -0
- package/dist/mcp/resources/engram-change-state.js +27 -0
- package/dist/mcp/resources/engram-change-state.js.map +1 -0
- package/dist/mcp/resources/engram-changes.d.ts +9 -0
- package/dist/mcp/resources/engram-changes.d.ts.map +1 -0
- package/dist/mcp/resources/engram-changes.js +17 -0
- package/dist/mcp/resources/engram-changes.js.map +1 -0
- package/dist/mcp/resources/engram-health.d.ts +9 -0
- package/dist/mcp/resources/engram-health.d.ts.map +1 -0
- package/dist/mcp/resources/engram-health.js +13 -0
- package/dist/mcp/resources/engram-health.js.map +1 -0
- package/dist/mcp/resources/engram-observation.d.ts +11 -0
- package/dist/mcp/resources/engram-observation.d.ts.map +1 -0
- package/dist/mcp/resources/engram-observation.js +18 -0
- package/dist/mcp/resources/engram-observation.js.map +1 -0
- package/dist/mcp/resources/engram-project.d.ts +11 -0
- package/dist/mcp/resources/engram-project.d.ts.map +1 -0
- package/dist/mcp/resources/engram-project.js +47 -0
- package/dist/mcp/resources/engram-project.js.map +1 -0
- package/dist/mcp/resources/engram-projects.d.ts +9 -0
- package/dist/mcp/resources/engram-projects.d.ts.map +1 -0
- package/dist/mcp/resources/engram-projects.js +13 -0
- package/dist/mcp/resources/engram-projects.js.map +1 -0
- package/dist/mcp/resources/engram-session.d.ts +11 -0
- package/dist/mcp/resources/engram-session.d.ts.map +1 -0
- package/dist/mcp/resources/engram-session.js +18 -0
- package/dist/mcp/resources/engram-session.js.map +1 -0
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +104 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/get-observation.d.ts +16 -0
- package/dist/mcp/tools/get-observation.d.ts.map +1 -0
- package/dist/mcp/tools/get-observation.js +21 -0
- package/dist/mcp/tools/get-observation.js.map +1 -0
- package/dist/mcp/tools/get-project-state.d.ts +16 -0
- package/dist/mcp/tools/get-project-state.d.ts.map +1 -0
- package/dist/mcp/tools/get-project-state.js +63 -0
- package/dist/mcp/tools/get-project-state.js.map +1 -0
- package/dist/mcp/tools/get-session.d.ts +16 -0
- package/dist/mcp/tools/get-session.d.ts.map +1 -0
- package/dist/mcp/tools/get-session.js +21 -0
- package/dist/mcp/tools/get-session.js.map +1 -0
- package/dist/mcp/tools/list-observations.d.ts +22 -0
- package/dist/mcp/tools/list-observations.d.ts.map +1 -0
- package/dist/mcp/tools/list-observations.js +23 -0
- package/dist/mcp/tools/list-observations.js.map +1 -0
- package/dist/mcp/tools/list-projects.d.ts +10 -0
- package/dist/mcp/tools/list-projects.d.ts.map +1 -0
- package/dist/mcp/tools/list-projects.js +14 -0
- package/dist/mcp/tools/list-projects.js.map +1 -0
- package/dist/mcp/tools/list-sessions.d.ts +19 -0
- package/dist/mcp/tools/list-sessions.d.ts.map +1 -0
- package/dist/mcp/tools/list-sessions.js +22 -0
- package/dist/mcp/tools/list-sessions.js.map +1 -0
- package/dist/mcp/tools/search-observations.d.ts +16 -0
- package/dist/mcp/tools/search-observations.d.ts.map +1 -0
- package/dist/mcp/tools/search-observations.js +16 -0
- package/dist/mcp/tools/search-observations.js.map +1 -0
- package/dist/schemas/health.d.ts +16 -0
- package/dist/schemas/health.d.ts.map +1 -0
- package/dist/schemas/health.js +7 -0
- package/dist/schemas/health.js.map +1 -0
- package/dist/schemas/observation.d.ts +34 -0
- package/dist/schemas/observation.d.ts.map +1 -0
- package/dist/schemas/observation.js +14 -0
- package/dist/schemas/observation.js.map +1 -0
- package/dist/schemas/project.d.ts +19 -0
- package/dist/schemas/project.d.ts.map +1 -0
- package/dist/schemas/project.js +8 -0
- package/dist/schemas/project.js.map +1 -0
- package/dist/schemas/session.d.ts +145 -0
- package/dist/schemas/session.d.ts.map +1 -0
- package/dist/schemas/session.js +24 -0
- package/dist/schemas/session.js.map +1 -0
- package/dist/schemas/timestamp.d.ts +3 -0
- package/dist/schemas/timestamp.d.ts.map +1 -0
- package/dist/schemas/timestamp.js +11 -0
- package/dist/schemas/timestamp.js.map +1 -0
- package/dist/sidebar/components/activity-feed.d.ts +13 -0
- package/dist/sidebar/components/activity-feed.d.ts.map +1 -0
- package/dist/sidebar/components/activity-feed.js +21 -0
- package/dist/sidebar/components/activity-feed.js.map +1 -0
- package/dist/sidebar/components/blockers.d.ts +17 -0
- package/dist/sidebar/components/blockers.d.ts.map +1 -0
- package/dist/sidebar/components/blockers.js +26 -0
- package/dist/sidebar/components/blockers.js.map +1 -0
- package/dist/sidebar/components/phase-progress.d.ts +13 -0
- package/dist/sidebar/components/phase-progress.d.ts.map +1 -0
- package/dist/sidebar/components/phase-progress.js +21 -0
- package/dist/sidebar/components/phase-progress.js.map +1 -0
- package/dist/sidebar/components/project-list.d.ts +16 -0
- package/dist/sidebar/components/project-list.d.ts.map +1 -0
- package/dist/sidebar/components/project-list.js +42 -0
- package/dist/sidebar/components/project-list.js.map +1 -0
- package/dist/sidebar/components/reactive-value.d.ts +4 -0
- package/dist/sidebar/components/reactive-value.d.ts.map +1 -0
- package/dist/sidebar/components/reactive-value.js +4 -0
- package/dist/sidebar/components/reactive-value.js.map +1 -0
- package/dist/sidebar/dashboard-launcher.d.ts +52 -0
- package/dist/sidebar/dashboard-launcher.d.ts.map +1 -0
- package/dist/sidebar/dashboard-launcher.js +125 -0
- package/dist/sidebar/dashboard-launcher.js.map +1 -0
- package/dist/sidebar/hooks/use-engram.d.ts +81 -0
- package/dist/sidebar/hooks/use-engram.d.ts.map +1 -0
- package/dist/sidebar/hooks/use-engram.js +423 -0
- package/dist/sidebar/hooks/use-engram.js.map +1 -0
- package/dist/sidebar/plugin.d.ts +85 -0
- package/dist/sidebar/plugin.d.ts.map +1 -0
- package/dist/sidebar/plugin.js +288 -0
- package/dist/sidebar/plugin.js.map +1 -0
- package/dist/stdio.d.ts +3 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +20 -0
- package/dist/stdio.js.map +1 -0
- package/dist/utils/errors.d.ts +19 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +27 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/format.d.ts +11 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +30 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/project-resolver.d.ts +21 -0
- package/dist/utils/project-resolver.d.ts.map +1 -0
- package/dist/utils/project-resolver.js +122 -0
- package/dist/utils/project-resolver.js.map +1 -0
- package/dist/utils/sdd-detector.d.ts +35 -0
- package/dist/utils/sdd-detector.d.ts.map +1 -0
- package/dist/utils/sdd-detector.js +115 -0
- package/dist/utils/sdd-detector.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Onirico Sistemas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
# Flema Engram para OpenCode
|
|
2
|
+
|
|
3
|
+
[](https://github.com/oniricosistemas/flema-engram/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-flema-engram-sidebar)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://github.com/oniricosistemas/flema-engram/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Un sidebar local y de solo lectura que mantiene visible el contexto reciente de
|
|
9
|
+
[Engram](https://github.com/Gentleman-Programming/engram) mientras trabajás dentro de OpenCode.
|
|
10
|
+
|
|
11
|
+
La canción **“Y aún yo te recuerdo”** inspiró artísticamente la idea central: que la
|
|
12
|
+
última memoria guardada siga presente en el contexto de desarrollo. Esta referencia
|
|
13
|
+
es un homenaje a esa inspiración; no implica afiliación, patrocinio ni titularidad
|
|
14
|
+
sobre la obra.
|
|
15
|
+
|
|
16
|
+
> **English summary:** Flema Engram is a local-first, read-only OpenCode
|
|
17
|
+
> plugin/sidebar that surfaces Engram health, project context, recent memories,
|
|
18
|
+
> blockers, and SDD progress. An MCP stdio adapter is included only as an optional
|
|
19
|
+
> integration path.
|
|
20
|
+
|
|
21
|
+
## Inicio rápido
|
|
22
|
+
|
|
23
|
+
### Requisitos
|
|
24
|
+
|
|
25
|
+
| Componente | Requisito |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| Node.js | 22 o posterior |
|
|
28
|
+
| OpenCode | 1.18.25 o posterior |
|
|
29
|
+
| Engram | Servicio HTTP local activo en `http://127.0.0.1:7437` |
|
|
30
|
+
| Proyecto | Alguna observación o sesión reciente que permita validar el nombre |
|
|
31
|
+
|
|
32
|
+
### 1. Instalá el paquete
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install -g opencode-flema-engram-sidebar
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Registrá el sidebar en `tui.json`
|
|
39
|
+
|
|
40
|
+
Configuración mínima:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
45
|
+
"plugin": ["opencode-flema-engram-sidebar"]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
OpenCode carga el destino `./tui` del paquete. Ese export apunta al módulo compilado
|
|
50
|
+
`dist/sidebar/plugin.js`; el export raíz no se usa como reemplazo del plugin TUI.
|
|
51
|
+
|
|
52
|
+
### 3. Iniciá Engram y abrí OpenCode en tu proyecto
|
|
53
|
+
|
|
54
|
+
El sidebar hace una carga inicial, vuelve a consultar cada 30 segundos de forma
|
|
55
|
+
predeterminada y permite refrescar manualmente con <kbd>Alt</kbd>+<kbd>R</kbd>.
|
|
56
|
+
|
|
57
|
+
## Qué es — y qué no es
|
|
58
|
+
|
|
59
|
+
| Sí es | No es |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| Un plugin para el slot `sidebar_content` de OpenCode | Una TUI independiente |
|
|
62
|
+
| Una vista local y de solo lectura sobre Engram | Un reemplazo de Engram |
|
|
63
|
+
| Un resumen de contexto, actividad y avance SDD | Un editor o gestor de memorias |
|
|
64
|
+
| Un cliente del HTTP local de Engram | Un servicio cloud o de sincronización |
|
|
65
|
+
| Un paquete con un adaptador MCP stdio opcional | Un producto centrado en MCP |
|
|
66
|
+
|
|
67
|
+
El sidebar es el producto principal. El comando MCP existe para integraciones
|
|
68
|
+
avanzadas y puede ignorarse por completo al usar el plugin de OpenCode.
|
|
69
|
+
|
|
70
|
+
## Cómo funciona
|
|
71
|
+
|
|
72
|
+
1. OpenCode carga el export `opencode-flema-engram-sidebar/tui`.
|
|
73
|
+
2. El plugin resuelve un candidato de proyecto y lo valida contra los proyectos
|
|
74
|
+
derivados de observaciones y sesiones recientes de Engram.
|
|
75
|
+
3. Consulta en paralelo salud, proyectos y observaciones del proyecto.
|
|
76
|
+
4. Ordena la actividad por actualización más reciente, detecta artefactos SDD y
|
|
77
|
+
reconoce bloqueos explícitos.
|
|
78
|
+
5. Renderiza texto dentro del sidebar de la sesión visible y conserva datos útiles
|
|
79
|
+
como `STALE` si una actualización posterior queda incompleta.
|
|
80
|
+
|
|
81
|
+
Las llamadas usan exclusivamente `GET` contra el Engram local. El plugin no guarda,
|
|
82
|
+
edita ni elimina memorias.
|
|
83
|
+
|
|
84
|
+
### Endpoints utilizados
|
|
85
|
+
|
|
86
|
+
| Propósito | Endpoint local |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| Salud | `GET /health` |
|
|
89
|
+
| Observaciones | `GET /observations/recent` |
|
|
90
|
+
| Sesiones para derivar proyectos | `GET /sessions/recent` |
|
|
91
|
+
|
|
92
|
+
El adaptador local usa un timeout de 5 segundos. Para mantener la vista acotada, el
|
|
93
|
+
sidebar solicita hasta 20 observaciones del proyecto; si la respuesta filtrada está
|
|
94
|
+
vacía o mezcla proyectos, usa una consulta sin filtro de hasta 100 registros y aplica
|
|
95
|
+
una coincidencia exacta local.
|
|
96
|
+
|
|
97
|
+
## Configuración
|
|
98
|
+
|
|
99
|
+
### Opciones soportadas por el plugin
|
|
100
|
+
|
|
101
|
+
Usá la forma `[plugin, options]` solamente cuando necesites opciones:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
106
|
+
"plugin": [
|
|
107
|
+
[
|
|
108
|
+
"opencode-flema-engram-sidebar",
|
|
109
|
+
{
|
|
110
|
+
"enabled": true,
|
|
111
|
+
"project": "mi-proyecto",
|
|
112
|
+
"pollInterval": 30000
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Opción | Tipo | Comportamiento |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `enabled` | `boolean` | `false` evita que el plugin registre el sidebar. Por defecto está habilitado. |
|
|
122
|
+
| `project` | `string` | Nombre exacto de proyecto Engram. Tiene la prioridad más alta. |
|
|
123
|
+
| `pollInterval` | `number` | Intervalo automático en milisegundos; debe ser mayor que cero. Por defecto: `30000`. |
|
|
124
|
+
|
|
125
|
+
No hay una opción TUI para cambiar la URL o el timeout del HTTP local. El plugin
|
|
126
|
+
incluido usa `http://127.0.0.1:7437` y 5 segundos respectivamente.
|
|
127
|
+
|
|
128
|
+
### Resolución del proyecto
|
|
129
|
+
|
|
130
|
+
La precedencia real es:
|
|
131
|
+
|
|
132
|
+
1. `project` no vacío en el `tui.json` que declara el plugin;
|
|
133
|
+
2. variable de entorno `ENGRAM_PROJECT` no vacía;
|
|
134
|
+
3. nombre normalizado del directorio de trabajo y, como segundo candidato automático,
|
|
135
|
+
su ruta absoluta normalizada.
|
|
136
|
+
|
|
137
|
+
```powershell
|
|
138
|
+
$env:ENGRAM_PROJECT = "mi-proyecto"
|
|
139
|
+
opencode .
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Cada candidato debe coincidir con un proyecto conocido por Engram. Se acepta una
|
|
143
|
+
coincidencia exacta o una única coincidencia sin distinguir mayúsculas. Un valor
|
|
144
|
+
explícito o de entorno inválido **no** cae silenciosamente al nombre del directorio.
|
|
145
|
+
Tampoco hay búsqueda difusa, recorrido de directorios padre, selector ni elección
|
|
146
|
+
automática del primer proyecto.
|
|
147
|
+
|
|
148
|
+
### Tema
|
|
149
|
+
|
|
150
|
+
El render del plugin usa el color de texto del tema activo que entrega OpenCode y no
|
|
151
|
+
impone una paleta ni crea archivos de tema propios. Los estados también se distinguen
|
|
152
|
+
por etiquetas e iconos, no solamente por color.
|
|
153
|
+
|
|
154
|
+
## Qué muestra el sidebar
|
|
155
|
+
|
|
156
|
+
### Health
|
|
157
|
+
|
|
158
|
+
| Estado | Significado |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| `CHECKING` | La carga inicial todavía no terminó. |
|
|
161
|
+
| `OK` | El HTTP local responde y las observaciones requeridas se obtuvieron. |
|
|
162
|
+
| `STALE` | Hay datos utilizables o previos, pero una etapa de la actualización quedó incompleta. |
|
|
163
|
+
| `ERROR` | Hubo respuesta parcial o un fallo de salud sin una carga completa. |
|
|
164
|
+
| `OFFLINE` | No pudo obtenerse información central de Engram. |
|
|
165
|
+
|
|
166
|
+
Los fallos muestran la etapa o endpoint relevante. Un error queda contenido en el
|
|
167
|
+
sidebar para no derribar el host de OpenCode.
|
|
168
|
+
|
|
169
|
+
### Proyecto y contexto detectado
|
|
170
|
+
|
|
171
|
+
Muestra el nombre validado del proyecto o `unresolved`. Cuando no puede resolverlo,
|
|
172
|
+
explica si falta configuración, el candidato no existe, es ambiguo o Engram estaba
|
|
173
|
+
offline durante la validación.
|
|
174
|
+
|
|
175
|
+
### Observaciones indexadas
|
|
176
|
+
|
|
177
|
+
La línea `Indexed observations` refleja las observaciones del proyecto cargadas para
|
|
178
|
+
la vista actual, dentro del límite acotado del sidebar; no debe interpretarse como un
|
|
179
|
+
contador histórico total de toda la base. Si no hay registros, se muestra un estado
|
|
180
|
+
vacío explícito.
|
|
181
|
+
|
|
182
|
+
### Avance SDD
|
|
183
|
+
|
|
184
|
+
Agrupa observaciones cuyo `topic_key` sigue `sdd/<cambio>/<artefacto>`. Reconoce las
|
|
185
|
+
fases `init`, `explore`, `proposal`, `spec`, `design`, `tasks`, `apply`, `verify` y
|
|
186
|
+
`archive`, incluidos los alias `apply-progress`, `verify-report` y `archive-report`.
|
|
187
|
+
Presenta el estado derivado y la secuencia de fases observadas. Si no hay cambios
|
|
188
|
+
activos o detectables, indica `No active SDD changes`.
|
|
189
|
+
|
|
190
|
+
### Bloqueos
|
|
191
|
+
|
|
192
|
+
Lista títulos de observaciones reconocidas como bloqueos por tipo, título o frases
|
|
193
|
+
explícitas como `status: blocked`, `blocker:`, `blocked by`, `depends on` o
|
|
194
|
+
`waiting for`. No infiere bloqueos a partir de sentimiento o contexto ambiguo.
|
|
195
|
+
|
|
196
|
+
### Actividad reciente
|
|
197
|
+
|
|
198
|
+
Muestra los títulos de las cinco observaciones más recientes del proyecto, ordenadas
|
|
199
|
+
por `updated_at` y luego por ID. Cuando no hay actividad, aparece un estado vacío.
|
|
200
|
+
|
|
201
|
+
### Última memoria guardada
|
|
202
|
+
|
|
203
|
+
No existe un panel duplicado para “última memoria”: la observación actualizada más
|
|
204
|
+
reciente ocupa el primer lugar de **Recent Activity**. La vista muestra su título, no
|
|
205
|
+
el contenido completo. Así mantiene presente la referencia más nueva sin convertir
|
|
206
|
+
el sidebar en un explorador de memorias.
|
|
207
|
+
|
|
208
|
+
### Refresh, carga y errores
|
|
209
|
+
|
|
210
|
+
- La primera carga se agenda una sola vez para el sidebar de la sesión visible.
|
|
211
|
+
- El polling automático usa `pollInterval` o 30 segundos.
|
|
212
|
+
- <kbd>Alt</kbd>+<kbd>R</kbd> vuelve a resolver el proyecto y repite todas las consultas.
|
|
213
|
+
- La tecla `r` sin modificadores queda libre para escribir en el prompt.
|
|
214
|
+
- `Refreshing…`, `Refreshed` y `Refresh incomplete` describen el resultado manual.
|
|
215
|
+
- Los datos anteriores se conservan como `STALE` cuando siguen siendo útiles.
|
|
216
|
+
- Un proyecto sin observaciones, sin cambios SDD o sin bloqueos tiene mensajes vacíos
|
|
217
|
+
propios; no se confunde con un error de conexión.
|
|
218
|
+
|
|
219
|
+
## Ejemplos de uso
|
|
220
|
+
|
|
221
|
+
### Detección automática desde el directorio
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
cd mi-proyecto
|
|
225
|
+
opencode .
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Si Engram conoce `mi-proyecto`, el sidebar valida ese nombre y carga sus memorias.
|
|
229
|
+
|
|
230
|
+
### Fijar un proyecto por workspace
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
235
|
+
"plugin": [
|
|
236
|
+
["opencode-flema-engram-sidebar", { "project": "backend-api" }]
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Esto es útil cuando el nombre del directorio no coincide con el proyecto guardado en
|
|
242
|
+
Engram. El valor debe coincidir; una configuración incorrecta queda como `unresolved`.
|
|
243
|
+
|
|
244
|
+
### Reducir la frecuencia de actualización
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
249
|
+
"plugin": [
|
|
250
|
+
["opencode-flema-engram-sidebar", { "pollInterval": 60000 }]
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
El sidebar actualizará cada 60 segundos y seguirá aceptando <kbd>Alt</kbd>+<kbd>R</kbd>.
|
|
256
|
+
|
|
257
|
+
## Instalación desde el código fuente
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
git clone https://github.com/oniricosistemas/flema-engram.git
|
|
261
|
+
cd flema-engram
|
|
262
|
+
npm install
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
El archivo versionado `tui.example.json` es la referencia para desarrollo desde el
|
|
266
|
+
código fuente y apunta directamente a:
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
271
|
+
"plugin": [["./src/sidebar/plugin.tsx", {}]]
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Creá tu copia local con `Copy-Item tui.example.json tui__.json` en PowerShell o
|
|
276
|
+
`cp tui.example.json tui__.json` en shells compatibles. `tui__.json` está ignorado
|
|
277
|
+
por Git a propósito: es la copia local para adaptar sin versionar rutas u opciones
|
|
278
|
+
específicas de tu máquina. Si tu entorno requiere el nombre `tui.json`, copiá allí el
|
|
279
|
+
contenido de `tui__.json` o fusioná su bloque `plugin` en tu configuración existente.
|
|
280
|
+
|
|
281
|
+
La ruta se resuelve desde el archivo que declara el plugin. En una configuración
|
|
282
|
+
global de Windows, reemplazá la ruta relativa de tu copia local por una URL absoluta:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
287
|
+
"plugin": [
|
|
288
|
+
["file:///D:/general/mcp-flema-engram/src/sidebar/plugin.tsx", {}]
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Preservá el resto de tus plugins y opciones al fusionar la configuración.
|
|
294
|
+
|
|
295
|
+
## Solución de problemas
|
|
296
|
+
|
|
297
|
+
### Engram aparece offline
|
|
298
|
+
|
|
299
|
+
- Confirmá que Engram escuche en `127.0.0.1:7437`.
|
|
300
|
+
- Probá `http://127.0.0.1:7437/health` desde la misma máquina.
|
|
301
|
+
- Revisá firewalls o proxies locales; el plugin no intenta conectarse a un servicio
|
|
302
|
+
cloud como alternativa.
|
|
303
|
+
- Corregí la causa y presioná <kbd>Alt</kbd>+<kbd>R</kbd>.
|
|
304
|
+
|
|
305
|
+
### El proyecto no se detecta
|
|
306
|
+
|
|
307
|
+
- Verificá el nombre real guardado en Engram.
|
|
308
|
+
- Definí `project` en `tui.json` si el workspace tiene otro nombre.
|
|
309
|
+
- Usá `ENGRAM_PROJECT` solamente cuando deba aplicar al proceso completo.
|
|
310
|
+
- Recordá la precedencia: `project` explícito gana sobre `ENGRAM_PROJECT`.
|
|
311
|
+
- Un proyecto sin observaciones ni sesiones recientes puede no aparecer en la lista
|
|
312
|
+
derivada que se usa para validarlo.
|
|
313
|
+
|
|
314
|
+
### Los datos parecen viejos
|
|
315
|
+
|
|
316
|
+
- Mirá si Health dice `STALE` y leé el detalle de etapa/endpoint.
|
|
317
|
+
- Esperá el próximo polling o usá <kbd>Alt</kbd>+<kbd>R</kbd>.
|
|
318
|
+
- Confirmá que la memoria pertenece exactamente al proyecto resuelto.
|
|
319
|
+
- El feed solo muestra cinco títulos y la consulta del sidebar está acotada a 20
|
|
320
|
+
observaciones.
|
|
321
|
+
|
|
322
|
+
### El plugin no carga
|
|
323
|
+
|
|
324
|
+
- Cerrá y reiniciá OpenCode después de cambiar `tui.json`.
|
|
325
|
+
- Validá el JSON y la ruta del plugin.
|
|
326
|
+
- Confirmá Node.js 22+ y OpenCode 1.18.25+.
|
|
327
|
+
- Para npm, usá el nombre exacto `opencode-flema-engram-sidebar`.
|
|
328
|
+
- Para fuente local, ejecutá `npm install` y comprobá que la ruta termine en
|
|
329
|
+
`src/sidebar/plugin.tsx`.
|
|
330
|
+
- Revisá la salida de arranque de OpenCode. El plugin no crea archivos de log propios.
|
|
331
|
+
|
|
332
|
+
### OpenCode no encuentra el export del paquete
|
|
333
|
+
|
|
334
|
+
- Confirmá que la versión instalada contiene el export `./tui`.
|
|
335
|
+
- Ejecutá `npm view opencode-flema-engram-sidebar exports` para inspeccionar la
|
|
336
|
+
metadata publicada.
|
|
337
|
+
- Si trabajás sobre un tarball local, verificá que incluya
|
|
338
|
+
`dist/sidebar/plugin.js` y `dist/sidebar/plugin.d.ts`.
|
|
339
|
+
- No agregues un fallback `main`: este paquete separa deliberadamente el plugin TUI,
|
|
340
|
+
la API raíz y el CLI MCP.
|
|
341
|
+
|
|
342
|
+
### Diagnóstico para contribuidores
|
|
343
|
+
|
|
344
|
+
```sh
|
|
345
|
+
npm run typecheck
|
|
346
|
+
npm test
|
|
347
|
+
npm run verify:package
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`typecheck` valida TypeScript sin emitir archivos; `test` ejecuta Vitest una vez; y
|
|
351
|
+
`verify:package` inspecciona un `npm pack --dry-run` contra el contenido compilado
|
|
352
|
+
existente. No hay una opción de log JSONL ni un archivo de diagnóstico del sidebar.
|
|
353
|
+
|
|
354
|
+
## Desarrollo y contribución
|
|
355
|
+
|
|
356
|
+
```sh
|
|
357
|
+
npm ci
|
|
358
|
+
npm run typecheck
|
|
359
|
+
npm test
|
|
360
|
+
npm run build
|
|
361
|
+
npm run verify:package
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
| Comando | Qué comprueba |
|
|
365
|
+
| --- | --- |
|
|
366
|
+
| `npm ci` | Instala exactamente el árbol de `package-lock.json`; es el usado por CI. |
|
|
367
|
+
| `npm install` | Instala dependencias y permite actualizar el lockfile en desarrollo. |
|
|
368
|
+
| `npm run typecheck` | Ejecuta `tsc --noEmit` con configuración estricta. |
|
|
369
|
+
| `npm test` | Ejecuta la suite unitaria y de integración con Vitest. |
|
|
370
|
+
| `npm run test:watch` | Mantiene Vitest activo durante el desarrollo. |
|
|
371
|
+
| `npm run test:coverage` | Genera cobertura mediante V8. |
|
|
372
|
+
| `npm run build` | Compila `src` a ESM, declaraciones y sourcemaps en `dist`. |
|
|
373
|
+
| `npm run verify:package` | Valida metadata y contenido del tarball sin publicarlo. Requiere un `dist` actualizado. |
|
|
374
|
+
| `npm run mcp` | Inicia desde fuente el adaptador MCP stdio opcional mediante `tsx`. |
|
|
375
|
+
|
|
376
|
+
La CI usa Node 22 y ejecuta, en este orden: typecheck, tests, build y verificación del
|
|
377
|
+
paquete. No publica artefactos ni necesita secretos. Antes de proponer un cambio:
|
|
378
|
+
|
|
379
|
+
- [ ] Mantené el sidebar como producto principal y el MCP como integración opcional.
|
|
380
|
+
- [ ] No introduzcas escrituras sobre Engram en el flujo de solo lectura.
|
|
381
|
+
- [ ] Agregá o actualizá pruebas para cambios de comportamiento.
|
|
382
|
+
- [ ] Ejecutá los cuatro checks de CI.
|
|
383
|
+
- [ ] No incluyas `src`, tests, OpenSpec, configs locales ni logs en el tarball.
|
|
384
|
+
|
|
385
|
+
## Empaquetado y publicación
|
|
386
|
+
|
|
387
|
+
La lista `files` permite publicar `dist`, `README.md` y `LICENSE`; npm agrega además
|
|
388
|
+
la metadata obligatoria. El verificador exige, como mínimo:
|
|
389
|
+
|
|
390
|
+
- API raíz: `dist/index.js` y `dist/index.d.ts`;
|
|
391
|
+
- plugin: `dist/sidebar/plugin.js` y `dist/sidebar/plugin.d.ts`;
|
|
392
|
+
- CLI opcional: `dist/stdio.js`;
|
|
393
|
+
- README, licencia y `package.json`.
|
|
394
|
+
|
|
395
|
+
`prepublishOnly` ejecuta automáticamente:
|
|
396
|
+
|
|
397
|
+
```sh
|
|
398
|
+
npm run typecheck && npm test && npm run build && npm run verify:package
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Para publicar necesitás permisos sobre
|
|
402
|
+
[`opencode-flema-engram-sidebar`](https://www.npmjs.com/package/opencode-flema-engram-sidebar),
|
|
403
|
+
una sesión npm válida y un `package.json` con versión todavía no publicada. Este
|
|
404
|
+
repositorio no configura publicación automática, tokens ni secretos.
|
|
405
|
+
|
|
406
|
+
## Integración MCP opcional
|
|
407
|
+
|
|
408
|
+
> Sección avanzada: no es necesaria para usar el sidebar.
|
|
409
|
+
|
|
410
|
+
La instalación global expone el comando stdio:
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
mcp-flema-engram
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Desde el repositorio puede iniciarse sin compilar con:
|
|
417
|
+
|
|
418
|
+
```sh
|
|
419
|
+
npm run mcp
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Ejemplo de configuración local de OpenCode, equivalente a `opencode.example.json`:
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"$schema": "https://opencode.ai/config.json",
|
|
427
|
+
"mcp": {
|
|
428
|
+
"engram": {
|
|
429
|
+
"type": "local",
|
|
430
|
+
"command": ["npm", "run", "mcp"],
|
|
431
|
+
"enabled": true
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
El servidor ofrece herramientas y recursos MCP de lectura para salud, proyectos,
|
|
438
|
+
observaciones, sesiones y cambios SDD. Sigue usando el mismo HTTP local de Engram;
|
|
439
|
+
no agrega persistencia ni sincronización cloud.
|
|
440
|
+
|
|
441
|
+
## Seguridad, privacidad y enfoque local-first
|
|
442
|
+
|
|
443
|
+
- Los datos de Engram permanecen en la máquina y el runtime consulta loopback.
|
|
444
|
+
- El sidebar no requiere credenciales, telemetría ni servicios cloud.
|
|
445
|
+
- No ejecuta operaciones de escritura sobre memorias.
|
|
446
|
+
- No crea logs propios con contenido de observaciones.
|
|
447
|
+
- El contenido de memorias puede ser sensible: protegé el acceso al proceso y puerto
|
|
448
|
+
local de Engram como protegerías cualquier herramienta de desarrollo.
|
|
449
|
+
- Las conexiones externas solo son necesarias para tareas ajenas al runtime local,
|
|
450
|
+
como instalar desde npm o abrir enlaces de documentación.
|
|
451
|
+
|
|
452
|
+
El repositorio contiene una clase experimental de adaptador cloud con operaciones no
|
|
453
|
+
implementadas. No forma parte del flujo del plugin ni constituye soporte cloud.
|
|
454
|
+
|
|
455
|
+
## Roadmap y alcance diferido
|
|
456
|
+
|
|
457
|
+
Están explícitamente fuera del MVP actual:
|
|
458
|
+
|
|
459
|
+
- dashboard visual y acciones/atajos para abrirlo;
|
|
460
|
+
- conexión remota mediante `opencode attach`;
|
|
461
|
+
- selector manual de proyecto, navegación `j`/`k` y más atajos globales;
|
|
462
|
+
- configuración de URL/timeout desde `tui.json`;
|
|
463
|
+
- sincronización cloud y edición de memorias.
|
|
464
|
+
|
|
465
|
+
## Licencia y enlaces
|
|
466
|
+
|
|
467
|
+
Distribuido bajo la [licencia MIT](./LICENSE).
|
|
468
|
+
|
|
469
|
+
- [Repositorio](https://github.com/oniricosistemas/flema-engram)
|
|
470
|
+
- [Issues](https://github.com/oniricosistemas/flema-engram/issues)
|
|
471
|
+
- [Paquete npm](https://www.npmjs.com/package/opencode-flema-engram-sidebar)
|
|
472
|
+
- [CI](https://github.com/oniricosistemas/flema-engram/actions/workflows/ci.yml)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EngramAdapter, HealthStatus, Project, Observation, ListObservationsOpts, SearchOpts, Session, ListSessionsOpts } from "./types.js";
|
|
2
|
+
export interface CloudAdapterOptions {
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
token?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class CloudEngramAdapter implements EngramAdapter {
|
|
7
|
+
private readonly baseUrl;
|
|
8
|
+
private readonly token;
|
|
9
|
+
constructor(opts?: CloudAdapterOptions);
|
|
10
|
+
health(): Promise<HealthStatus>;
|
|
11
|
+
listProjects(): Promise<Project[]>;
|
|
12
|
+
listObservations(_opts?: ListObservationsOpts): Promise<Observation[]>;
|
|
13
|
+
getObservation(_id: number): Promise<Observation | null>;
|
|
14
|
+
searchObservations(_query: string, _opts?: SearchOpts): Promise<Observation[]>;
|
|
15
|
+
listSessions(_opts?: ListSessionsOpts): Promise<Session[]>;
|
|
16
|
+
getSession(_sessionId: string): Promise<Session | null>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=cloud.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/adapters/cloud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,UAAU,EACV,OAAO,EACP,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;gBAE/B,IAAI,CAAC,EAAE,mBAAmB;IAYhC,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAgB/B,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAIlC,gBAAgB,CAAC,KAAK,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAItE,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAIxD,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAI9E,YAAY,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAI1D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CAG9D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NotImplemented } from "../utils/errors.js";
|
|
2
|
+
export class CloudEngramAdapter {
|
|
3
|
+
baseUrl;
|
|
4
|
+
token;
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
if (opts?.baseUrl) {
|
|
7
|
+
try {
|
|
8
|
+
new URL(opts.baseUrl);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error(`Invalid cloud baseUrl: ${opts.baseUrl}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
this.baseUrl = opts?.baseUrl;
|
|
15
|
+
this.token = opts?.token;
|
|
16
|
+
}
|
|
17
|
+
async health() {
|
|
18
|
+
if (!this.baseUrl) {
|
|
19
|
+
return { local: { available: false }, cloud: { available: false } };
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const headers = {};
|
|
23
|
+
if (this.token)
|
|
24
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
27
|
+
const res = await fetch(`${this.baseUrl}/health`, { signal: controller.signal, headers }).finally(() => clearTimeout(timeoutId));
|
|
28
|
+
return { local: { available: false }, cloud: { available: res.ok } };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { local: { available: false }, cloud: { available: false } };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async listProjects() {
|
|
35
|
+
throw new NotImplemented("CloudEngramAdapter.listProjects");
|
|
36
|
+
}
|
|
37
|
+
async listObservations(_opts) {
|
|
38
|
+
throw new NotImplemented("CloudEngramAdapter.listObservations");
|
|
39
|
+
}
|
|
40
|
+
async getObservation(_id) {
|
|
41
|
+
throw new NotImplemented("CloudEngramAdapter.getObservation");
|
|
42
|
+
}
|
|
43
|
+
async searchObservations(_query, _opts) {
|
|
44
|
+
throw new NotImplemented("CloudEngramAdapter.searchObservations");
|
|
45
|
+
}
|
|
46
|
+
async listSessions(_opts) {
|
|
47
|
+
throw new NotImplemented("CloudEngramAdapter.listSessions");
|
|
48
|
+
}
|
|
49
|
+
async getSession(_sessionId) {
|
|
50
|
+
throw new NotImplemented("CloudEngramAdapter.getSession");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=cloud.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud.js","sourceRoot":"","sources":["../../src/adapters/cloud.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAOpD,MAAM,OAAO,kBAAkB;IACZ,OAAO,CAAqB;IAC5B,KAAK,CAAqB;IAE3C,YAAY,IAA0B;QACpC,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC;QACtE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAA2B,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;YAClE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;YAC7D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,SAAS,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;YACjI,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC;QACtE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,cAAc,CAAC,iCAAiC,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,KAA4B;QACjD,MAAM,IAAI,cAAc,CAAC,qCAAqC,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,GAAW;QAC9B,MAAM,IAAI,cAAc,CAAC,mCAAmC,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,MAAc,EAAE,KAAkB;QACzD,MAAM,IAAI,cAAc,CAAC,uCAAuC,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAwB;QACzC,MAAM,IAAI,cAAc,CAAC,iCAAiC,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,UAAkB;QACjC,MAAM,IAAI,cAAc,CAAC,+BAA+B,CAAC,CAAC;IAC5D,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EngramAdapter, HealthStatus, Project, Observation, ListObservationsOpts, SearchOpts, Session, ListSessionsOpts } from "./types.js";
|
|
2
|
+
export declare class CompositeEngramAdapter implements EngramAdapter {
|
|
3
|
+
private readonly adapters;
|
|
4
|
+
private readonly cache;
|
|
5
|
+
private readonly maxCacheSize;
|
|
6
|
+
constructor(adapters: EngramAdapter[], opts?: {
|
|
7
|
+
maxCacheSize?: number;
|
|
8
|
+
});
|
|
9
|
+
health(): Promise<HealthStatus>;
|
|
10
|
+
listProjects(): Promise<Project[]>;
|
|
11
|
+
listObservations(opts?: ListObservationsOpts): Promise<Observation[]>;
|
|
12
|
+
getObservation(id: number): Promise<Observation | null>;
|
|
13
|
+
searchObservations(query: string, opts?: SearchOpts): Promise<Observation[]>;
|
|
14
|
+
listSessions(opts?: ListSessionsOpts): Promise<Session[]>;
|
|
15
|
+
getSession(sessionId: string): Promise<Session | null>;
|
|
16
|
+
private invokeWithFallback;
|
|
17
|
+
clearCache(): void;
|
|
18
|
+
private cached;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=composite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../src/adapters/composite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,UAAU,EACV,OAAO,EACP,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAWpB,qBAAa,sBAAuB,YAAW,aAAa;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,QAAQ,EAAE,aAAa,EAAE,EAAE,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;IAajE,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAM/B,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAMlC,gBAAgB,CAAC,IAAI,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAOrE,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAMvD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAO5E,YAAY,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAOzD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YAM9C,kBAAkB;IA4BhC,UAAU,IAAI,IAAI;YAIJ,MAAM;CAmBrB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { EngramUnavailable } from "../utils/errors.js";
|
|
2
|
+
const CACHE_TTL_MS = 30_000;
|
|
3
|
+
const DEFAULT_MAX_CACHE_SIZE = 100;
|
|
4
|
+
export class CompositeEngramAdapter {
|
|
5
|
+
adapters;
|
|
6
|
+
cache = new Map();
|
|
7
|
+
maxCacheSize;
|
|
8
|
+
constructor(adapters, opts) {
|
|
9
|
+
const filtered = adapters.filter((a) => a != null);
|
|
10
|
+
if (filtered.length === 0) {
|
|
11
|
+
throw new Error("CompositeEngramAdapter requires at least one adapter");
|
|
12
|
+
}
|
|
13
|
+
this.adapters = filtered;
|
|
14
|
+
const size = opts?.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE;
|
|
15
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
16
|
+
throw new Error(`Invalid maxCacheSize: ${size}`);
|
|
17
|
+
}
|
|
18
|
+
this.maxCacheSize = size;
|
|
19
|
+
}
|
|
20
|
+
async health() {
|
|
21
|
+
return this.invokeWithFallback((adapter) => adapter.health());
|
|
22
|
+
}
|
|
23
|
+
async listProjects() {
|
|
24
|
+
return this.cached("listProjects", () => this.invokeWithFallback((adapter) => adapter.listProjects()));
|
|
25
|
+
}
|
|
26
|
+
async listObservations(opts) {
|
|
27
|
+
const key = `listObservations:${JSON.stringify(opts ?? {}, Object.keys(opts ?? {}).sort())}`;
|
|
28
|
+
return this.cached(key, () => this.invokeWithFallback((adapter) => adapter.listObservations(opts)));
|
|
29
|
+
}
|
|
30
|
+
async getObservation(id) {
|
|
31
|
+
return this.cached(`getObservation:${id}`, () => this.invokeWithFallback((adapter) => adapter.getObservation(id)));
|
|
32
|
+
}
|
|
33
|
+
async searchObservations(query, opts) {
|
|
34
|
+
const key = `searchObservations:${query}:${JSON.stringify(opts ?? {}, Object.keys(opts ?? {}).sort())}`;
|
|
35
|
+
return this.cached(key, () => this.invokeWithFallback((adapter) => adapter.searchObservations(query, opts)));
|
|
36
|
+
}
|
|
37
|
+
async listSessions(opts) {
|
|
38
|
+
const key = `listSessions:${JSON.stringify(opts ?? {}, Object.keys(opts ?? {}).sort())}`;
|
|
39
|
+
return this.cached(key, () => this.invokeWithFallback((adapter) => adapter.listSessions(opts)));
|
|
40
|
+
}
|
|
41
|
+
async getSession(sessionId) {
|
|
42
|
+
return this.cached(`getSession:${sessionId}`, () => this.invokeWithFallback((adapter) => adapter.getSession(sessionId)));
|
|
43
|
+
}
|
|
44
|
+
async invokeWithFallback(call) {
|
|
45
|
+
let lastError;
|
|
46
|
+
for (let i = 0; i < this.adapters.length; i++) {
|
|
47
|
+
const adapter = this.adapters[i];
|
|
48
|
+
if (!adapter)
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
return await call(adapter);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
55
|
+
if (!(err instanceof EngramUnavailable)) {
|
|
56
|
+
throw new EngramUnavailable(`Adapter call failed: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : undefined);
|
|
57
|
+
}
|
|
58
|
+
lastError = err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw new EngramUnavailable(`All adapters failed`, lastError);
|
|
62
|
+
}
|
|
63
|
+
clearCache() {
|
|
64
|
+
this.cache.clear();
|
|
65
|
+
}
|
|
66
|
+
async cached(key, fn) {
|
|
67
|
+
const entry = this.cache.get(key);
|
|
68
|
+
if (entry && Date.now() < entry.expiresAt) {
|
|
69
|
+
return entry.value;
|
|
70
|
+
}
|
|
71
|
+
const value = await fn();
|
|
72
|
+
this.cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
73
|
+
// Evict oldest entries when cache exceeds max size
|
|
74
|
+
while (this.cache.size > this.maxCacheSize) {
|
|
75
|
+
const firstKey = this.cache.keys().next().value;
|
|
76
|
+
if (firstKey) {
|
|
77
|
+
this.cache.delete(firstKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=composite.js.map
|