oficina-opencode 2.0.0 → 2.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/README.md +174 -24
- package/dist/core/atomic.d.ts +85 -0
- package/dist/core/atomic.d.ts.map +1 -0
- package/dist/core/atomic.js +186 -0
- package/dist/core/atomic.js.map +1 -0
- package/dist/core/bus.d.ts +76 -56
- package/dist/core/bus.d.ts.map +1 -1
- package/dist/core/bus.js +141 -100
- package/dist/core/bus.js.map +1 -1
- package/dist/core/catalog.d.ts +27 -0
- package/dist/core/catalog.d.ts.map +1 -1
- package/dist/core/catalog.js +41 -6
- package/dist/core/catalog.js.map +1 -1
- package/dist/core/cost.d.ts +275 -0
- package/dist/core/cost.d.ts.map +1 -0
- package/dist/core/cost.js +724 -0
- package/dist/core/cost.js.map +1 -0
- package/dist/core/docsync.d.ts +124 -0
- package/dist/core/docsync.d.ts.map +1 -0
- package/dist/core/docsync.js +191 -0
- package/dist/core/docsync.js.map +1 -0
- package/dist/core/evidence.d.ts +314 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +602 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fsx.d.ts.map +1 -1
- package/dist/core/fsx.js +7 -1
- package/dist/core/fsx.js.map +1 -1
- package/dist/core/gates.d.ts +9 -2
- package/dist/core/gates.d.ts.map +1 -1
- package/dist/core/gates.js +19 -3
- package/dist/core/gates.js.map +1 -1
- package/dist/core/ids.d.ts +48 -0
- package/dist/core/ids.d.ts.map +1 -0
- package/dist/core/ids.js +87 -0
- package/dist/core/ids.js.map +1 -0
- package/dist/core/leases.d.ts +45 -0
- package/dist/core/leases.d.ts.map +1 -1
- package/dist/core/leases.js +85 -17
- package/dist/core/leases.js.map +1 -1
- package/dist/core/office.d.ts +72 -1
- package/dist/core/office.d.ts.map +1 -1
- package/dist/core/office.js +173 -6
- package/dist/core/office.js.map +1 -1
- package/dist/core/paths.d.ts +11 -1
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +28 -4
- package/dist/core/paths.js.map +1 -1
- package/dist/core/recovery.d.ts +87 -0
- package/dist/core/recovery.d.ts.map +1 -0
- package/dist/core/recovery.js +325 -0
- package/dist/core/recovery.js.map +1 -0
- package/dist/core/sandbox.d.ts +179 -0
- package/dist/core/sandbox.d.ts.map +1 -0
- package/dist/core/sandbox.js +546 -0
- package/dist/core/sandbox.js.map +1 -0
- package/dist/core/state.d.ts +7 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +15 -1
- package/dist/core/state.js.map +1 -1
- package/dist/core/stores.d.ts +65 -0
- package/dist/core/stores.d.ts.map +1 -0
- package/dist/core/stores.js +115 -0
- package/dist/core/stores.js.map +1 -0
- package/dist/core/tripwire.d.ts +87 -0
- package/dist/core/tripwire.d.ts.map +1 -0
- package/dist/core/tripwire.js +203 -0
- package/dist/core/tripwire.js.map +1 -0
- package/dist/core/types.d.ts +33 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -0
- package/dist/core/types.js.map +1 -1
- package/dist/hooks/scopeGuard.d.ts +86 -13
- package/dist/hooks/scopeGuard.d.ts.map +1 -1
- package/dist/hooks/scopeGuard.js +296 -62
- package/dist/hooks/scopeGuard.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +158 -0
- package/dist/tools/index.js.map +1 -1
- package/docs/COMPATIBILITY.md +98 -0
- package/docs/GARANTIAS.md +190 -0
- package/docs/PLAN-V2.1.md +144 -0
- package/docs/QUE-ES.md +15 -0
- package/docs/catalog-facts.json +27 -0
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -5,18 +5,32 @@
|
|
|
5
5
|
### Una oficina de agentes para OpenCode donde el trabajo no se detiene cuando el modelo dice «terminé».
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/oficina-opencode)
|
|
8
|
-
[](https://github.com/SOCRAMbt/
|
|
8
|
+
[](https://github.com/SOCRAMbt/AI-OFFICE/tree/main/LICENSE)
|
|
9
9
|
[](https://nodejs.org)
|
|
10
|
-
[](https://github.com/SOCRAMbt/
|
|
10
|
+
[](https://github.com/SOCRAMbt/AI-OFFICE/tree/main/tests)
|
|
11
11
|
[](https://vitest.dev)
|
|
12
|
-
[](https://github.com/SOCRAMbt/
|
|
12
|
+
[](https://github.com/SOCRAMbt/AI-OFFICE/blob/main/tsconfig.json)
|
|
13
13
|
|
|
14
|
-
**
|
|
14
|
+
**Una malla de coordinadores + peer mesh sobre un roster de roles.**
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
<!-- BEGIN GENERATED: facts -->
|
|
17
|
+
| | |
|
|
18
|
+
|---|---|
|
|
19
|
+
| **Paquete** | `oficina-opencode` v2.1.0 |
|
|
20
|
+
| **Tools** | 16 |
|
|
21
|
+
| **Hooks** | 7 |
|
|
22
|
+
| **API de plugin** | `@opencode-ai/plugin@1.15.11` |
|
|
23
|
+
| **Node** | >=20 |
|
|
24
|
+
| **Catálogo** | 258 agentes resolubles en 18 divisiones |
|
|
25
|
+
| **Divisiones** | academic, design, engineering, finance, game-development, gis, healthcare, marketing, paid-media, product, project-management, research, sales, spatial-computing, specialized, strategy, support, testing |
|
|
26
|
+
| **Límites** | 20 cards · 40 llamadas · 20 spawns · 10 agentes |
|
|
27
|
+
| **Cobertura mínima** | 60% |
|
|
28
|
+
<!-- END GENERATED: facts -->
|
|
29
|
+
|
|
30
|
+
> **¿Preferís la versión en humano?** → [`docs/QUE-ES.md`](https://github.com/SOCRAMbt/AI-OFFICE/blob/main/docs/QUE-ES.md) — qué es, qué hace y por qué, sin jerga.
|
|
31
|
+
> **¿Preferís el porqué técnico?** → [`docs/ARCHITECTURE.md`](https://github.com/SOCRAMbt/AI-OFFICE/blob/main/docs/ARCHITECTURE.md)
|
|
32
|
+
|
|
33
|
+
[Documentación](#arquitectura) · [Garantías](https://github.com/SOCRAMbt/AI-OFFICE/blob/main/docs/GARANTIAS.md) · [Compatibilidad](https://github.com/SOCRAMbt/AI-OFFICE/blob/main/docs/COMPATIBILITY.md) · [Instalación](#instalación) · [Uso](#uso) · [CLI](#cli-operación)
|
|
20
34
|
|
|
21
35
|
</div>
|
|
22
36
|
|
|
@@ -141,6 +155,24 @@ Resolución real contra el catálogo:
|
|
|
141
155
|
|
|
142
156
|
Reproducible: `npx tsx scripts/demo-roles.ts`
|
|
143
157
|
|
|
158
|
+
### El benchmark: ¿la complejidad se justifica?
|
|
159
|
+
|
|
160
|
+
Un matching textual puede elegir bien al rol y aun así no ser el mejor para la tarea. La pregunta no es *"¿funciona?"* sino **"¿vale la pena?"**. El benchmark compara tres estrategias sobre 21 tareas con resultados de referencia:
|
|
161
|
+
|
|
162
|
+
| Estrategia | hit@1 | hit@3 | MRR |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| **El matcher de la oficina** | **71%** | 86% | **0.778** |
|
|
165
|
+
| Keywords (los pesos de la v1) | 48% | 81% | 0.623 |
|
|
166
|
+
| Solo el nombre del rol | 52% | 76% | 0.639 |
|
|
167
|
+
|
|
168
|
+
El matcher completo le gana por **23 puntos** de hit@1 a la versión anterior. La complejidad se justifica, y ahora está dicho con números en vez de con adjetivos.
|
|
169
|
+
|
|
170
|
+
También se probó una ponderación por **IDF** (los tokens raros pesan más) y **no se quedó**: bajaba el hit@1 a 67%. El código lo documenta y el benchmark lo demuestra. La complejidad que no se justifica hay que sacarla, no dejarla dormida en el repo.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npm run bench:selection
|
|
174
|
+
```
|
|
175
|
+
|
|
144
176
|
### El panel: primario + complementos
|
|
145
177
|
|
|
146
178
|
Un objetivo casi nunca es de un solo dominio. *"Formulario de login en React con validación y accesibilidad"* necesita un frontend (que no sabe de ARIA) **y** un especialista en accesibilidad (que no sabe de React).
|
|
@@ -266,6 +298,67 @@ El que recibe un pedido de ayuda **hace su propio triage** — no acepta por cor
|
|
|
266
298
|
|
|
267
299
|
---
|
|
268
300
|
|
|
301
|
+
## El scope es una restricción, no una instrucción
|
|
302
|
+
|
|
303
|
+
La versión anterior del guard comparaba strings: `"...".match(/^src\//)`. Eso se eludía por tres vías, y las tres eran reales.
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
// El bug: la ruta se comparaba sin resolver.
|
|
307
|
+
const relative = path.relative(projectRoot, absolute)
|
|
308
|
+
if (!matchesScope(relative, mayTouch)) throw ...
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
| Escape | Antes | Ahora |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| Symlink a fuera del proyecto | `src/link.ts` matcheaba `src/**` | Se resuelve el symlink y **se revalida** la contención |
|
|
314
|
+
| `..` con `mayTouch: ["**"]` | `**` matcheaba cualquier string | `**` significa *el proyecto*, nunca el filesystem |
|
|
315
|
+
| Path absoluto | `relative` empezaba en `..` y con `**` pasaba | La contención se verifica antes de cualquier glob |
|
|
316
|
+
|
|
317
|
+
Ahora la ruta se **resuelve** (absoluta → normalizada → symlinks resueltos → `realpath`) y recién ahí se decide, con `fail-closed`.
|
|
318
|
+
|
|
319
|
+
Además:
|
|
320
|
+
|
|
321
|
+
- **Todas las tools que escriben**, no solo `edit`/`write`. Los patches multi-archivo se chequean **todos**, no solo el primero.
|
|
322
|
+
- **Los comandos de shell** se analizan antes de ejecutarse: redirecciones, `cp`, `mv`, `rm`, `sed -i`, `dd`, `curl -o`, `git checkout`, interpreters embebidos.
|
|
323
|
+
- **Los subagentes heredan el scope** por la cadena de padres de sesión. Delegar no es un escalón para salirse.
|
|
324
|
+
- **Los archivos sensibles** (`.env`, `.npmrc`, `.git/**`, claves privadas) nunca son escribibles, pase lo que pase el scope de la card.
|
|
325
|
+
- **El tripwire** fotografía el árbol antes de un comando y compara después: detecta lo que el guard no pudo ver.
|
|
326
|
+
|
|
327
|
+
> Lo que **no** se promete está en [`docs/GARANTIAS.md`](https://github.com/SOCRAMbt/AI-OFFICE/blob/main/docs/GARANTIAS.md). Un shell con interpreter embebido puede escribir afuera; el tripwire lo detecta después, pero no lo revierte. Preferimos decirlo a vender un aislamiento que no existe.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Una caída no destruye la corrida
|
|
332
|
+
|
|
333
|
+
La oficina escribe en disco, en varios archivos, sin transacción. Un proceso que muere en el momento equivocado deja el estado incoherente. `reconcile()` compara **estado vs artefactos vs filesystem**:
|
|
334
|
+
|
|
335
|
+
| Situación | Qué hace |
|
|
336
|
+
|---|---|
|
|
337
|
+
| Card en `review` sin `handoff.md` | El agente murió entregando. Se avisa. |
|
|
338
|
+
| Card en `done` sin `gates.md` | Se cerró sin gate. **Crítico.** |
|
|
339
|
+
| Card en progreso sin owner | Nadie la va a terminar. **Crítico.** |
|
|
340
|
+
| Lease de una card que no existe | Se libera. |
|
|
341
|
+
| Lease de una card terminada | Se libera. |
|
|
342
|
+
| Agente `working` sobre una card `done` | Se libera el agente. |
|
|
343
|
+
| Card bloqueada por dependencia irresoluble | Se reporta. |
|
|
344
|
+
|
|
345
|
+
Lo que **no** hace: completar cards ni inventar evidencia. Un sistema que se auto-repara inventando pruebas es peor que uno que dice *"esto está roto"*.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## El costo se controla, no solo la cantidad de agentes
|
|
350
|
+
|
|
351
|
+
Limitar el número de agentes **no** es controlar el costo: un agente que relee el mismo contexto veinte veces cuesta diez veces lo que uno que lo hace bien.
|
|
352
|
+
|
|
353
|
+
- El costo real viene del motor (`AssistantMessage.cost`), no se estima.
|
|
354
|
+
- Se contabiliza por **card**, por **agente** y por **fase** (implementación, review, reparación).
|
|
355
|
+
- Presupuestos por corrida, por card y por fase.
|
|
356
|
+
- Estimación previa a una corrida grande.
|
|
357
|
+
- Detección de **trabajo redundante**: releer el mismo path, repetir el mismo comando.
|
|
358
|
+
- **Freno de emergencia**: `oficina_stop` detiene la corrida; el auto-continuación y el despacho se niegan mientras esté detenida.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
269
362
|
## Capa 1 — El briefing: qué sabe cada agente antes de arrancar
|
|
270
363
|
|
|
271
364
|
Un subagente arranca con el contexto vacío. Si el Commander le pasa un prompt suelta (*"implementá el login"*), el agente tiene que adivinar: qué sabe del proyecto, qué espera de él, qué puede tocar, a quién preguntarle si se traba, y qué tiene que entregar. Adivinar es la fuente de todo el trabajo que se repite y de los agentes que se salen del scope.
|
|
@@ -363,7 +456,7 @@ No acepta por cortesía. Seis preguntas, en orden:
|
|
|
363
456
|
|
|
364
457
|
---
|
|
365
458
|
|
|
366
|
-
## Los
|
|
459
|
+
## Los 7 hooks
|
|
367
460
|
|
|
368
461
|
Acá es donde el protocolo deja de ser una intención.
|
|
369
462
|
|
|
@@ -373,14 +466,18 @@ Acá es donde el protocolo deja de ser una intención.
|
|
|
373
466
|
| `tool.execute.before` | Bloquea escrituras fuera del scope de la card | «No toques eso» pasa de instrucción a restricción |
|
|
374
467
|
| `experimental.chat.system.transform` | Inyecta el estado de la oficina en cada llamada | El agente no repregunta lo que ya sabe ni repite trabajo |
|
|
375
468
|
| `experimental.session.compacting` | Reinyecta el estado **leído del disco** al compactar | Sin esto, la oficina se olvida a mitad de camino |
|
|
469
|
+
| `tool.execute.after` | Tripwire: fotografía el árbol antes de un comando y compara después | Detecta lo que el guard no pudo ver |
|
|
470
|
+
| `permission.ask` | Segunda capa de enforcement, a nivel del framework | Deniega en el motor, no solo en el hook propio |
|
|
471
|
+
| `event` → `message.updated` | Contabilidad de costo con el número real del motor | El costo no se estima: el motor ya lo sabe |
|
|
472
|
+
| `experimental.compaction.autocontinue` | Freno de emergencia sobre la auto-continuación | Sin esto, una corrida se quema sola en tokens |
|
|
376
473
|
|
|
377
474
|
El hook de auto-continuación tiene guardas duras: solo reanuda si queda trabajo real, tope de 25 auto-reanudaciones por sesión, cooldown de 1,5 s entre reanudaciones, y nunca reanuda una corrida `paused`, `blocked` o `delivered`.
|
|
378
475
|
|
|
379
476
|
---
|
|
380
477
|
|
|
381
|
-
## Los
|
|
478
|
+
## Los 7 gates
|
|
382
479
|
|
|
383
|
-
|
|
480
|
+
Se ejecutan de verdad; el motor interpreta los exit codes.
|
|
384
481
|
|
|
385
482
|
| # | Fase | Node | Python | Go | Rust |
|
|
386
483
|
|---|---|---|---|---|---|
|
|
@@ -390,6 +487,33 @@ De la skill `verification-loop`. Se ejecutan de verdad; el motor interpreta los
|
|
|
390
487
|
| 4 | Tests | `npm run test` | `pytest` | `go test` | `cargo test` |
|
|
391
488
|
| 5 | Secretos | escaneo en código | ← | ← | ← |
|
|
392
489
|
| 6 | Diff review | `git diff --stat` | ← | ← | ← |
|
|
490
|
+
| 7 | Requisitos | cada criterio de aceptación con su evidencia | ← | ← | ← |
|
|
491
|
+
|
|
492
|
+
### La pirámide de verificación
|
|
493
|
+
|
|
494
|
+
Los seis gates técnicos dicen que **el código está sano**. Dicen nada de si el **objetivo del usuario está cumplido**: un proyecto puede compilar, pasar todos sus tests y tener la autenticación mal porque ningún test contemplaba ese escenario.
|
|
495
|
+
|
|
496
|
+
| Nivel | Qué comprueba |
|
|
497
|
+
|---|---|
|
|
498
|
+
| 1 — Estático | Tipos, lint, secretos, dependencias |
|
|
499
|
+
| 2 — Comportamiento | Unitarias, integración, regresión, casos límite |
|
|
500
|
+
| 3 — Requisitos | Cada criterio de aceptación enlazado a su evidencia |
|
|
501
|
+
| 4 — Auditoría | Revisión independiente, riesgos, coherencia de la evidencia |
|
|
502
|
+
|
|
503
|
+
El gate 7 es el que cierra el círculo: **una card no se cierra si falta evidencia de un requisito obligatorio**, aunque los otros seis estén en verde.
|
|
504
|
+
|
|
505
|
+
### El grafo requisito → evidencia
|
|
506
|
+
|
|
507
|
+
Cada criterio de aceptación es un requisito. Cada evidencia —un artefacto, un test, un gate, una inspección, una auditoría— se vincula a un requisito con un nivel. El grafo responde *qué falta para poder cerrar*:
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
T-00 · Implementar login
|
|
511
|
+
[✓] endpoint de login nivel 2 artefacto
|
|
512
|
+
[ ] sin errores de validación nivel 0 — FALTA
|
|
513
|
+
[!] cubierto por revisión independiente nivel 4 auditoría
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Un artefacto llega a nivel 2; un test, a 2; un gate, a 1 (o 2 si es la fase `test`); una inspección, a 3; una auditoría —con revisor distinto del owner—, a 4. Una auto-revisión con cara de auditoría se **rechaza**: es peor que no tener auditoría.
|
|
393
517
|
|
|
394
518
|
**Reglas de honestidad** — no negociables:
|
|
395
519
|
|
|
@@ -405,9 +529,12 @@ De la skill `verification-loop`. Se ejecutan de verdad; el motor interpreta los
|
|
|
405
529
|
|
|
406
530
|
Los implementa el código. Un prompt puede pedirle al modelo que sea cuidadoso; el motor no puede romperlos.
|
|
407
531
|
|
|
532
|
+
> **Ocho invariantes duros**, y se evalúan **donde corresponde**: el de cierre en el cierre, el de presupuesto al gastar, el de ayuda al enrutar. Evaluar un invariante de `DONE` con la card todavía en `review` lo deja vacío — y así estaba antes: el invariante `gate_required_before_done` no disparaba nunca. Ahora se valida contra el estado al que la card llegaría.
|
|
533
|
+
|
|
408
534
|
| Invariante | Valor |
|
|
409
535
|
|---|---|
|
|
410
536
|
| `gate_required_before_done` | `true` — sin gates en `PASS` no hay `DONE` |
|
|
537
|
+
| `evidence_required_before_done` | `true` — sin evidencia de los requisitos obligatorios no hay `DONE`. *Fail-closed*: si la evidencia no se evaluó, no se cierra |
|
|
411
538
|
| `reviewer_neq_owner` | `true` — no hay auto-aprobación |
|
|
412
539
|
| `max_concurrent_agents` | 10 base · 14 hard cap |
|
|
413
540
|
| `max_spawn_depth` | 2 niveles bajo el coordinador |
|
|
@@ -439,9 +566,9 @@ Las preguntas de un especialista al coordinador **van por el bus**, no interrump
|
|
|
439
566
|
|
|
440
567
|
---
|
|
441
568
|
|
|
442
|
-
## Los
|
|
569
|
+
## Los 16 tools
|
|
443
570
|
|
|
444
|
-
|
|
571
|
+
Dieciséis, no treinta. Cada tool que se suma es ruido en el contexto de todos los agentes.
|
|
445
572
|
|
|
446
573
|
| Tool | Qué hace |
|
|
447
574
|
|---|---|
|
|
@@ -458,6 +585,9 @@ Trece, no treinta. Cada tool que se suma es ruido en el contexto de todos los ag
|
|
|
458
585
|
| `oficina_escalate` | Escala al usuario con 2-3 opciones concretas y recomendación |
|
|
459
586
|
| `oficina_ficha` | Resuelve un rol a ficha y **despliega** el agente con su briefing |
|
|
460
587
|
| `oficina_brief` | Muestra el briefing y el **panel de roles** sin desplegar: decisión del Comandante |
|
|
588
|
+
| `oficina_evidence` | El grafo de requisitos a evidencia: qué falta para poder cerrar |
|
|
589
|
+
| `oficina_cost` | Tokens y USD por card, agente y fase, con los presupuestos |
|
|
590
|
+
| `oficina_stop` | Freno de emergencia: detiene o reanuda la corrida |
|
|
461
591
|
|
|
462
592
|
---
|
|
463
593
|
|
|
@@ -486,13 +616,13 @@ npm install -g oficina-opencode
|
|
|
486
616
|
### Local (desarrollo)
|
|
487
617
|
|
|
488
618
|
```bash
|
|
489
|
-
git clone https://github.com/SOCRAMbt/
|
|
490
|
-
cd
|
|
619
|
+
git clone https://github.com/SOCRAMbt/AI-OFFICE.git
|
|
620
|
+
cd AI-OFFICE
|
|
491
621
|
npm install
|
|
492
622
|
npm run build
|
|
493
623
|
|
|
494
624
|
cd ~/.config/opencode
|
|
495
|
-
npm install file:/ruta/a/
|
|
625
|
+
npm install file:/ruta/a/AI-OFFICE
|
|
496
626
|
```
|
|
497
627
|
|
|
498
628
|
Y el entry point en `~/.config/opencode/plugins/oficina.js`:
|
|
@@ -601,7 +731,7 @@ El formato de artefactos es compatible con OFICINA v1 (la skill). El plugin **mi
|
|
|
601
731
|
|
|
602
732
|
## Código
|
|
603
733
|
|
|
604
|
-
|
|
734
|
+
31 archivos TypeScript · 18 módulos en `core/` · 1 toolset · 7 hooks.
|
|
605
735
|
|
|
606
736
|
```
|
|
607
737
|
src/
|
|
@@ -617,13 +747,22 @@ src/
|
|
|
617
747
|
│ ├── factory.ts # generación de roles con briefing de 10 secciones
|
|
618
748
|
│ ├── briefing.ts # el contrato de información del agente
|
|
619
749
|
│ ├── router.ts # enrutado + rotación anti-monotonía
|
|
620
|
-
│ ├── gates.ts # runner de los
|
|
750
|
+
│ ├── gates.ts # runner de los 7 gates + escaneo de secretos
|
|
751
|
+
│ ├── sandbox.ts # resolucion real de rutas + scope + sensibles
|
|
752
|
+
│ ├── atomic.ts # primitivas atomicas: O_EXCL, locks, seq
|
|
753
|
+
│ ├── evidence.ts # grafo requisito -> evidencia
|
|
754
|
+
│ ├── cost.ts # ledger de tokens y USD
|
|
755
|
+
│ ├── recovery.ts # reconciliacion estado vs artefactos
|
|
756
|
+
│ ├── tripwire.ts # deteccion de escrituras coladas
|
|
757
|
+
│ ├── stores.ts # persistencia de evidencia y costo
|
|
758
|
+
│ ├── docsync.ts # fuente unica de verdad de las cifras
|
|
621
759
|
│ ├── retry.ts # política de 3 golpes
|
|
622
760
|
│ ├── office.ts # la fachada que amarra todo
|
|
623
761
|
│ ├── paths.ts # resolución de project root
|
|
624
762
|
│ ├── fsx.ts # IO atómico
|
|
763
|
+
│ ├── ids.ts # ids seguros para usar como nombre de archivo
|
|
625
764
|
│ └── log.ts # logging estructurado
|
|
626
|
-
├── tools/index.ts # las
|
|
765
|
+
├── tools/index.ts # las 16 tools
|
|
627
766
|
└── hooks/
|
|
628
767
|
├── sessionIdle.ts # auto-continuación
|
|
629
768
|
├── scopeGuard.ts # enforcement de scope
|
|
@@ -640,8 +779,10 @@ Una sola, y es peer del host: `@opencode-ai/plugin`. Zod entra por `tool.schema`
|
|
|
640
779
|
## Testing
|
|
641
780
|
|
|
642
781
|
```bash
|
|
643
|
-
npm test #
|
|
644
|
-
npm run coverage #
|
|
782
|
+
npm test # 385 tests, 21 archivos
|
|
783
|
+
npm run coverage # cobertura por línea
|
|
784
|
+
npm run docs:check # la documentación coincide con el código
|
|
785
|
+
npm run bench:selection # el matcher le gana a las alternativas simples
|
|
645
786
|
npm run typecheck # tsc --noEmit, strict
|
|
646
787
|
npm run build # tsc → dist/
|
|
647
788
|
```
|
|
@@ -650,14 +791,21 @@ npm run build # tsc → dist/
|
|
|
650
791
|
|---|---|
|
|
651
792
|
| `bus.test.ts` | Append-only, ack, TTL, idempotencia, broadcast, prioridad, atomicidad |
|
|
652
793
|
| `state.test.ts` | Transiciones, invariantes duros, selección de card, ciclo de corrida |
|
|
653
|
-
| `gates.test.ts` | Detección de tooling, las
|
|
794
|
+
| `gates.test.ts` | Detección de tooling, las 7 fases, N/A con motivo, consolidación, redacción |
|
|
654
795
|
| `router.test.ts` | Enrutado, rotación, reviewer ≠ owner, política de 3 golpes, presupuesto |
|
|
655
796
|
| `leases.test.ts` | Adquisición, heartbeat, expiración, double-claim, reasignación |
|
|
656
797
|
| `catalog.test.ts` | Frontmatter, secciones, extracción, matching ponderado, panel, factory |
|
|
657
|
-
| `catalogReal.test.ts` | El catálogo real de
|
|
798
|
+
| `catalogReal.test.ts` | El catálogo real de agency-agents: estructuras, matching, división |
|
|
658
799
|
| `briefing.test.ts` | Orden, mínimo, las 10 secciones, los 4 artefactos, INCOMPLETO |
|
|
659
800
|
| `office.test.ts` | Ciclo completo end-to-end, invariantes, artefactos en disco |
|
|
660
801
|
| `scopeGuard.test.ts` | Globs, extracción de path, enforcement de scope |
|
|
802
|
+
| `sandbox.test.ts` | **Adversarial**: symlinks, traversal, absolutos, sensibles, shell |
|
|
803
|
+
| `concurrency.test.ts` | Doble claim simultáneo, seq único, idempotencia atómica |
|
|
804
|
+
| `recovery.test.ts` | Entregas perdidas, invariantes vulnerados, huérfanos, reinicio |
|
|
805
|
+
| `evidence.test.ts` | El grafo requisito → evidencia y los 4 niveles |
|
|
806
|
+
| `cost.test.ts` | Ledger, presupuestos por fase, trabajo redundante |
|
|
807
|
+
| `tripwire.test.ts` | Detección de escrituras coladas por shell |
|
|
808
|
+
| `docsync.test.ts` | La documentación coincide con el código |
|
|
661
809
|
| `plugin.test.ts` | El plugin contra un host mockeado: tools, hooks, auto-resume |
|
|
662
810
|
| `migration.test.ts` | Migración v1 → v2 sin pérdida de datos |
|
|
663
811
|
| `cli.test.ts` | init, status, board, install, audit, reset, idempotencia |
|
|
@@ -666,6 +814,8 @@ npm run build # tsc → dist/
|
|
|
666
814
|
|
|
667
815
|
## Licencia
|
|
668
816
|
|
|
669
|
-
MIT — ver [LICENSE](https://github.com/SOCRAMbt/
|
|
817
|
+
MIT — ver [LICENSE](https://github.com/SOCRAMbt/AI-OFFICE/tree/main/LICENSE).
|
|
670
818
|
|
|
671
819
|
El diseño se apoya en los patrones de `durable-execution` y `workflow-engine-design`: estado durable antes de los efectos, leases con TTL, transiciones nombradas explícitamente y at-least-once con reconciliación por idempotency key.
|
|
820
|
+
|
|
821
|
+
Lo que **no** se promete está escrito en [`docs/GARANTIAS.md`](https://github.com/SOCRAMbt/AI-OFFICE/blob/main/docs/GARANTIAS.md): un plugin de aplicación no es un sandbox, y un README que vende más de lo que el código hace es peor que uno modesto.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitivas atómicas sobre disco.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué existe
|
|
5
|
+
*
|
|
6
|
+
* `writeAtomic` (tmp + rename) garantiza que un lector nunca vea un JSON a
|
|
7
|
+
* medio escribir. No garantiza que **dos escritores no se pisen**. El
|
|
8
|
+
* patron "leo, evaluo, escribo" — un TOCTOU — es la raiz de las carreras
|
|
9
|
+
* que existian:
|
|
10
|
+
*
|
|
11
|
+
* - El lease: dos agentes leian "no hay lease", los dos pasaban el chequeo,
|
|
12
|
+
* los dos escribian, y el segundo gano. El primero creia que tenia la
|
|
13
|
+
* card y el trabajo se hacia dos veces.
|
|
14
|
+
* - El `seq` del bus: dos emisores concurrentes calculaban el mismo
|
|
15
|
+
* `max + 1` y dejaban dos mensajes con el mismo seq.
|
|
16
|
+
* - La idempotencia: el "existe ya?" se hacia antes de escribir, asi que
|
|
17
|
+
* dos envios concurrentes con la misma clave creaban dos mensajes.
|
|
18
|
+
*
|
|
19
|
+
* ## Las tres primitivas
|
|
20
|
+
*
|
|
21
|
+
* - `writeExclusive`: crea el archivo con `O_EXCL`. Si ya existe, **no**
|
|
22
|
+
* lo pisa: devuelve el contenido que hay. Es la primitiva de "reclamar".
|
|
23
|
+
* - `withLock`: mutex por directorio. `mkdir` es atomico en POSIX y en
|
|
24
|
+
* Windows, asi que solo un proceso puede tomar el lock. Tiene un
|
|
25
|
+
* breaker de locks vencidos, porque un proceso muerto no suelta el lock.
|
|
26
|
+
* - `allocateSeq`: numero de secuencia unico, con la asignacion y la
|
|
27
|
+
* escritura del mensaje bajo el mismo lock.
|
|
28
|
+
*
|
|
29
|
+
* ## Lo que NO resuelve
|
|
30
|
+
*
|
|
31
|
+
* Nada de esto es un sistema de transacciones. No hay rollback, ni
|
|
32
|
+
* commit en dos fases, ni journal. Es lo que hace falta para que las
|
|
33
|
+
* invariantes que el sistema ya declara (un lease, un seq, una clave de
|
|
34
|
+
* idempotencia) sean verdad en vez de una intencion.
|
|
35
|
+
*/
|
|
36
|
+
export interface ExclusiveWriteResult {
|
|
37
|
+
/** true si este proceso creo el archivo. */
|
|
38
|
+
created: boolean;
|
|
39
|
+
/** Contenido final del archivo (el propio, o el que ya estaba). */
|
|
40
|
+
contents: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Escribe un archivo de forma exclusiva.
|
|
44
|
+
*
|
|
45
|
+
* Con `O_EXCL`, el kernel garantiza que solo un proceso puede crearlo:
|
|
46
|
+
* los demas reciben `EEXIST` en vez de pisar. Es la primitiva atomica de
|
|
47
|
+
* "reclamar un recurso con nombre".
|
|
48
|
+
*/
|
|
49
|
+
export declare function writeExclusive(filePath: string, contents: string): Promise<ExclusiveWriteResult>;
|
|
50
|
+
/** ¿Existe un archivo? */
|
|
51
|
+
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
52
|
+
export interface LockOptions {
|
|
53
|
+
/** Cuanto esperar por el lock antes de rendirse. */
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
/** A partir de cuando un lock se considera muerto y se puede romper. */
|
|
56
|
+
staleMs?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Mutex por directorio.
|
|
60
|
+
*
|
|
61
|
+
* `mkdir` es atomico: de miles de procesos, uno solo recibe exito. Los
|
|
62
|
+
* demas ven `EEXIST` y esperan.
|
|
63
|
+
*
|
|
64
|
+
* ## Locks vencidos
|
|
65
|
+
*
|
|
66
|
+
* Si el proceso que tomo el lock muere, el directorio queda ahi para
|
|
67
|
+
* siempre. Por eso un lock mas viejo que `staleMs` se rompe: se asume que
|
|
68
|
+
* su dueno murio. Sin eso, un crash deja la oficina trabada para siempre,
|
|
69
|
+
* que es peor que el riesgo de romper un lock de otro proceso vivo (y ese
|
|
70
|
+
* proceso, si sigue vivo, esta haciendo su trabajo y no necesita el lock
|
|
71
|
+
* para otra cosa).
|
|
72
|
+
*/
|
|
73
|
+
export declare function withLock<T>(lockPath: string, fn: () => Promise<T>, options?: LockOptions): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Asigna un numero de secuencia unico dentro de un directorio.
|
|
76
|
+
*
|
|
77
|
+
* La asignacion y la escritura tienen que ocurrir **bajo el mismo lock**.
|
|
78
|
+
* Si el numero se reservara y el lock se soltara antes de escribir, el
|
|
79
|
+
* siguiente emisor no veria el archivo todavia no escrito y calcularia el
|
|
80
|
+
* mismo numero.
|
|
81
|
+
*/
|
|
82
|
+
export declare function allocateSeq(channelDir: string, write: (seq: number) => Promise<void>, options?: LockOptions): Promise<number>;
|
|
83
|
+
/** Path del lock de un canal. */
|
|
84
|
+
export declare function seqLockPath(channelDir: string): string;
|
|
85
|
+
//# sourceMappingURL=atomic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../../src/core/atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAKH,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CActG;AAED,0BAA0B;AAC1B,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAOD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,CAAC,CAAC,CAgEZ;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EACrC,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,iCAAiC;AACjC,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitivas atómicas sobre disco.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué existe
|
|
5
|
+
*
|
|
6
|
+
* `writeAtomic` (tmp + rename) garantiza que un lector nunca vea un JSON a
|
|
7
|
+
* medio escribir. No garantiza que **dos escritores no se pisen**. El
|
|
8
|
+
* patron "leo, evaluo, escribo" — un TOCTOU — es la raiz de las carreras
|
|
9
|
+
* que existian:
|
|
10
|
+
*
|
|
11
|
+
* - El lease: dos agentes leian "no hay lease", los dos pasaban el chequeo,
|
|
12
|
+
* los dos escribian, y el segundo gano. El primero creia que tenia la
|
|
13
|
+
* card y el trabajo se hacia dos veces.
|
|
14
|
+
* - El `seq` del bus: dos emisores concurrentes calculaban el mismo
|
|
15
|
+
* `max + 1` y dejaban dos mensajes con el mismo seq.
|
|
16
|
+
* - La idempotencia: el "existe ya?" se hacia antes de escribir, asi que
|
|
17
|
+
* dos envios concurrentes con la misma clave creaban dos mensajes.
|
|
18
|
+
*
|
|
19
|
+
* ## Las tres primitivas
|
|
20
|
+
*
|
|
21
|
+
* - `writeExclusive`: crea el archivo con `O_EXCL`. Si ya existe, **no**
|
|
22
|
+
* lo pisa: devuelve el contenido que hay. Es la primitiva de "reclamar".
|
|
23
|
+
* - `withLock`: mutex por directorio. `mkdir` es atomico en POSIX y en
|
|
24
|
+
* Windows, asi que solo un proceso puede tomar el lock. Tiene un
|
|
25
|
+
* breaker de locks vencidos, porque un proceso muerto no suelta el lock.
|
|
26
|
+
* - `allocateSeq`: numero de secuencia unico, con la asignacion y la
|
|
27
|
+
* escritura del mensaje bajo el mismo lock.
|
|
28
|
+
*
|
|
29
|
+
* ## Lo que NO resuelve
|
|
30
|
+
*
|
|
31
|
+
* Nada de esto es un sistema de transacciones. No hay rollback, ni
|
|
32
|
+
* commit en dos fases, ni journal. Es lo que hace falta para que las
|
|
33
|
+
* invariantes que el sistema ya declara (un lease, un seq, una clave de
|
|
34
|
+
* idempotencia) sean verdad en vez de una intencion.
|
|
35
|
+
*/
|
|
36
|
+
import fs from "node:fs/promises";
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
/**
|
|
39
|
+
* Escribe un archivo de forma exclusiva.
|
|
40
|
+
*
|
|
41
|
+
* Con `O_EXCL`, el kernel garantiza que solo un proceso puede crearlo:
|
|
42
|
+
* los demas reciben `EEXIST` en vez de pisar. Es la primitiva atomica de
|
|
43
|
+
* "reclamar un recurso con nombre".
|
|
44
|
+
*/
|
|
45
|
+
export async function writeExclusive(filePath, contents) {
|
|
46
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
47
|
+
let handle;
|
|
48
|
+
try {
|
|
49
|
+
handle = await fs.open(filePath, "wx");
|
|
50
|
+
await handle.writeFile(contents, "utf8");
|
|
51
|
+
return { created: true, contents };
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err.code !== "EEXIST")
|
|
55
|
+
throw err;
|
|
56
|
+
const existing = await fs.readFile(filePath, "utf8").catch(() => "");
|
|
57
|
+
return { created: false, contents: existing };
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await handle?.close().catch(() => { });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** ¿Existe un archivo? */
|
|
64
|
+
export async function fileExists(filePath) {
|
|
65
|
+
try {
|
|
66
|
+
await fs.access(filePath);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Espera un poco. */
|
|
74
|
+
function sleep(ms) {
|
|
75
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Mutex por directorio.
|
|
79
|
+
*
|
|
80
|
+
* `mkdir` es atomico: de miles de procesos, uno solo recibe exito. Los
|
|
81
|
+
* demas ven `EEXIST` y esperan.
|
|
82
|
+
*
|
|
83
|
+
* ## Locks vencidos
|
|
84
|
+
*
|
|
85
|
+
* Si el proceso que tomo el lock muere, el directorio queda ahi para
|
|
86
|
+
* siempre. Por eso un lock mas viejo que `staleMs` se rompe: se asume que
|
|
87
|
+
* su dueno murio. Sin eso, un crash deja la oficina trabada para siempre,
|
|
88
|
+
* que es peor que el riesgo de romper un lock de otro proceso vivo (y ese
|
|
89
|
+
* proceso, si sigue vivo, esta haciendo su trabajo y no necesita el lock
|
|
90
|
+
* para otra cosa).
|
|
91
|
+
*/
|
|
92
|
+
export async function withLock(lockPath, fn, options = {}) {
|
|
93
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
94
|
+
const staleMs = options.staleMs ?? 30_000;
|
|
95
|
+
const deadline = Date.now() + timeoutMs;
|
|
96
|
+
// El lock vive como directorio. Su padre puede no existir todavia (el
|
|
97
|
+
// canal del bus se crea con el primer mensaje), y `mkdir` sin `recursive`
|
|
98
|
+
// falla con ENOENT si el padre falta.
|
|
99
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
100
|
+
let acquired = false;
|
|
101
|
+
while (Date.now() < deadline) {
|
|
102
|
+
try {
|
|
103
|
+
await fs.mkdir(lockPath);
|
|
104
|
+
acquired = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const code = err.code;
|
|
109
|
+
if (code === "EEXIST") {
|
|
110
|
+
// Alguien mas tiene el lock. Normal.
|
|
111
|
+
}
|
|
112
|
+
else if (code === "EPERM") {
|
|
113
|
+
// ── Trampa de Windows ──
|
|
114
|
+
//
|
|
115
|
+
// Cuando el dueno anterior esta borrando el lock, un `mkdir`
|
|
116
|
+
// concurrente no da EEXIST: da EPERM, porque el path existe pero
|
|
117
|
+
// esta en un estado invalido. Es una carrera transitoria, no un
|
|
118
|
+
// error de permisos: se reintenta.
|
|
119
|
+
//
|
|
120
|
+
// Sin este caso, en Windows dos emisores concurrentes del bus se
|
|
121
|
+
// rompian con EPERM en vez de serializarse.
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
// ¿El lock esta vencido?
|
|
127
|
+
try {
|
|
128
|
+
const stat = await fs.stat(lockPath);
|
|
129
|
+
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
130
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => { });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// El lock desaparecio entre el mkdir y el stat: reintentar.
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Espera con jitter: evita que los procesos se alineen en la carrera.
|
|
139
|
+
await sleep(15 + Math.floor(Math.random() * 35));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!acquired) {
|
|
143
|
+
throw new Error(`[oficina] no se pudo tomar el lock ${lockPath} en ${timeoutMs}ms`);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return await fn();
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
// `rmdir` en vez de `rm -r`: el lock esta siempre vacio, y una sola
|
|
150
|
+
// llamada al sistema deja menos ventana para la carrera de Windows.
|
|
151
|
+
await fs.rmdir(lockPath).catch(async () => {
|
|
152
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => { });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Asigna un numero de secuencia unico dentro de un directorio.
|
|
158
|
+
*
|
|
159
|
+
* La asignacion y la escritura tienen que ocurrir **bajo el mismo lock**.
|
|
160
|
+
* Si el numero se reservara y el lock se soltara antes de escribir, el
|
|
161
|
+
* siguiente emisor no veria el archivo todavia no escrito y calcularia el
|
|
162
|
+
* mismo numero.
|
|
163
|
+
*/
|
|
164
|
+
export async function allocateSeq(channelDir, write, options = {}) {
|
|
165
|
+
const lockPath = path.join(channelDir, ".seq.lock");
|
|
166
|
+
return withLock(lockPath, async () => {
|
|
167
|
+
const entries = await fs.readdir(channelDir).catch(() => []);
|
|
168
|
+
let max = 0;
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
const m = /^(\d{4,})-/.exec(entry);
|
|
171
|
+
if (!m)
|
|
172
|
+
continue;
|
|
173
|
+
const n = Number.parseInt(m[1], 10);
|
|
174
|
+
if (Number.isFinite(n) && n > max)
|
|
175
|
+
max = n;
|
|
176
|
+
}
|
|
177
|
+
const seq = max + 1;
|
|
178
|
+
await write(seq);
|
|
179
|
+
return seq;
|
|
180
|
+
}, options);
|
|
181
|
+
}
|
|
182
|
+
/** Path del lock de un canal. */
|
|
183
|
+
export function seqLockPath(channelDir) {
|
|
184
|
+
return path.join(channelDir, ".seq.lock");
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic.js","sourceRoot":"","sources":["../../src/core/atomic.ts"],"names":[],"mappings":"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"}
|