@otixe/pi-oficinas 0.0.0-stage → 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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.es.md +49 -0
- package/README.md +48 -2
- package/bin/equipo_config.py +138 -0
- package/bin/oficina +2539 -0
- package/bin/oficina_distribucion.py +64 -0
- package/bin/oficina_layout.py +19 -0
- package/bin/oficina_ordenar.py +100 -0
- package/examples/.env.example +8 -0
- package/examples/SOUL.md +16 -0
- package/examples/distribucion.json +14 -0
- package/examples/equipo.json +29 -0
- package/examples/worker.md +11 -0
- package/extensions/context-leader-adapter.ts +186 -0
- package/extensions/context-pending.ts +59 -0
- package/extensions/context-relaunch.ts +137 -0
- package/extensions/context70.ts +117 -0
- package/extensions/secretaria-cerebro-client.ts +36 -0
- package/extensions/secretaria-core.ts +453 -0
- package/extensions/secretaria-correspondence-flow.ts +335 -0
- package/extensions/secretaria-correspondence-reservation.ts +227 -0
- package/extensions/secretaria-correspondence.ts +255 -0
- package/extensions/secretaria-cost.ts +63 -0
- package/extensions/secretaria-flow.ts +406 -0
- package/extensions/secretaria-leader-registry.ts +93 -0
- package/extensions/secretaria-monitor.ts +130 -0
- package/extensions/secretaria-state.ts +155 -0
- package/extensions/secretaria-vigilancia.ts +87 -0
- package/extensions/secretaria.ts +1129 -0
- package/extensions/vm-turno.ts +368 -0
- package/lib/cerebro-identity.ts +469 -0
- package/lib/team-config.ts +175 -0
- package/media/preview.png +0 -0
- package/package.json +57 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (unreleased staging)
|
|
4
|
+
|
|
5
|
+
- Extract five independently loadable Pi packages from current template/body sources.
|
|
6
|
+
- Replace installation-specific configuration and credentials with explicit examples/environment.
|
|
7
|
+
- Preserve fail-closed identity, durable plan ACK, tool membership and session/pane gates.
|
|
8
|
+
- Remove private canonical-team and layout-v1 compatibility.
|
|
9
|
+
- Add neutral examples, English/Spanish docs and isolated validation.
|
|
10
|
+
- No automatic deployment or publication.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Otixe
|
|
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.es.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @otixe/pi-oficinas
|
|
2
|
+
|
|
3
|
+
Una sola sesión Pi programa, revisa e investiga a la vez. Convertí esa sobrecarga en un equipo persistente: cada oficina conserva historial, identidad, credenciales y bandeja propios, con relevos verificados antes de empezar el trabajo.
|
|
4
|
+
|
|
5
|
+
[English](README.md) · MIT
|
|
6
|
+
|
|
7
|
+
## Dolores que resuelve
|
|
8
|
+
|
|
9
|
+
- Una sesión haciendo todos los roles → coordinación persistente líder/oficinas.
|
|
10
|
+
- Confusión de credenciales o sesiones → roles explícitos, key propia y gate de apertura con nonce fresco.
|
|
11
|
+
- Trabajo pendiente invisible → timbres, correspondencia y turnos locales de VM.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## Instalación en un comando
|
|
16
|
+
|
|
17
|
+
Desde un checkout local revisado:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pi install ./packages/pi-oficinas
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Requisitos y configuración
|
|
24
|
+
|
|
25
|
+
Pi 1.4.0, Python 3.10+, Herdr y su integración oficial (`herdr integration install pi`).
|
|
26
|
+
|
|
27
|
+
1. Adaptá `examples/equipo.json` y `examples/distribucion.json` (sólo schema 2), con rutas absolutas dentro de tu base. Pinneá `pi_bin` o instalá `<base>/runtime/node_modules/.bin/pi`: nunca se adivina el runtime global.
|
|
28
|
+
2. Configurá `PI_OFICINAS_EQUIPO` como archivo propio sin symlink. El pointer `.env` de cada cuerpo debe ser único y privado (`0600`). Declarará `AGENT_NAME`, `BODY_ROLE`, `AGENT_ENABLED`, variable de credencial propia y `SOUL_PATH`; `examples/.env.example` no trae credenciales.
|
|
29
|
+
3. Prepará/revisá cuerpos, fichas líder/oficina idénticas, `.pi/APPEND_SYSTEM.md` y `.pi/oficina-body`. El SOUL y la ficha de ejemplo están **inactivos**: no autorizan identidades de producción. La CLI no crea cuerpos automáticamente.
|
|
30
|
+
4. Proveé una integración confiable con `cb_whoami` y `cb_vault_read`. Configurá `CEREBRO_BASE`, `CEREBRO_AUTH_HEADER` (default `X-API-Key`) y `PI_OFICINAS_HUMAN` (default `human`).
|
|
31
|
+
5. Desde el package, ejecutá `python3 bin/oficina --equipo /absolute/path/equipo.json abrir worker` en el pane líder revisado. El gate verifica resultados frescos de identidad/SOUL propio, modelo/thinking, tools, sesión y propiedad del pane. El marker solo no autoriza nada.
|
|
32
|
+
|
|
33
|
+
Sin equipo/cuerpo explícito coincidente, secretaría queda inactiva. Siguen los campos opcionales `pi_coding_agent_dir`, `pi_coding_session_dir`, `relanzar_modo`. Mini puede compartir sólo la identidad de su padre por opt-in local; `PI_OFICINAS_MINI_MODEL` permite pin de modelo canónico. Nunca heredar keys de otro rol.
|
|
34
|
+
|
|
35
|
+
## Verificación y rollback
|
|
36
|
+
|
|
37
|
+
Para quitarlo, `pi remove SOURCE` y reiniciá/recargá tu sesión de prueba; preservá logs, snapshots y sesiones.
|
|
38
|
+
|
|
39
|
+
Configuración del monorepo y validación reproducible: [README](https://github.com/otixe/pi-oficinas/blob/main/README.es.md), [VALIDATION](https://github.com/otixe/pi-oficinas/blob/main/VALIDATION.md).
|
|
40
|
+
|
|
41
|
+
## Seguridad y límites
|
|
42
|
+
|
|
43
|
+
Revisá el código antes de instalar: las extensiones ejecutan con permisos del proceso, **no en un sandbox**. El gate no reemplaza autorización del backend o aislamiento del sistema operativo. La CLI no crea cuerpos automáticamente; los perfiles neutros están inactivos y el marker solo no autoriza nada. Nunca heredar keys de otro rol. No se incluye cliente Cerebro, credenciales ni almas reales.
|
|
44
|
+
|
|
45
|
+
Validado en Pi 1.4.0; los cinco paquetes host son peerDependencies `"*"`, nunca copias incluidas. Peers wildcard no prometen compatibilidad con todas las versiones del host. Las pruebas usan Pi/Herdr reales y modelo/backend simulados: **no certifican autenticación productiva**. La instalación requiere revisar fuente/trust.
|
|
46
|
+
|
|
47
|
+
Para archivos locales, extraé el tarball e instalá su directorio `package`: Pi 1.4.0 no carga sources de extensión `.tgz` aunque `pi install` tenga éxito. La instalación literal devuelve 0; la carga/print falla con exit 1. El README raíz muestra el comando de extracción soportado.
|
|
48
|
+
|
|
49
|
+
La instalación desde registry usa `pi install npm:@otixe/pi-oficinas@0.1.0` una vez liberada esa versión por el publicador. Los sources npm y URLs de repositorio/galería requieren publicación autorizada por separado; un checkout local no los vuelve disponibles.
|
package/README.md
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @otixe/pi-oficinas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One Pi session is coding, reviewing and researching at once. Split it into a persistent team: each office keeps its own session history, identity, credentials and inbox, with checked handoffs before work begins.
|
|
4
|
+
|
|
5
|
+
[Español](README.es.md) · MIT
|
|
6
|
+
|
|
7
|
+
## Problems it removes
|
|
8
|
+
|
|
9
|
+
- One session juggling every role → persistent leader/office coordination.
|
|
10
|
+
- Accidental credential or session mix-ups → explicit roles, own-key resolution and nonce-bound opening gates.
|
|
11
|
+
- Invisible pending work → task bells, correspondence and local VM-turn coordination.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## Install in one command
|
|
16
|
+
|
|
17
|
+
From a reviewed local checkout:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pi install ./packages/pi-oficinas
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Requirements and configuration
|
|
24
|
+
|
|
25
|
+
Pi 1.4.0, Python 3.10+, Herdr and its official Pi integration (`herdr integration install pi`).
|
|
26
|
+
|
|
27
|
+
1. Adapt `examples/equipo.json` and `examples/distribucion.json` (schema 2 only). Use absolute paths under your own team base. Set a local `pi_bin` runtime pin or provision `<base>/runtime/node_modules/.bin/pi`; no global-runtime guess.
|
|
28
|
+
2. Set `PI_OFICINAS_EQUIPO` to an owned, nonsymlink configuration. Each body’s `.env` pointer must be unique and private (`0600`). Configure `AGENT_NAME`, `BODY_ROLE`, `AGENT_ENABLED`, own credential variable and `SOUL_PATH`; `examples/.env.example` has no credentials.
|
|
29
|
+
3. Provision/review body directories, matching leader/body agent profiles, `.pi/APPEND_SYSTEM.md` and `.pi/oficina-body`. The neutral example SOUL/profile are **inactive**, not production identities. Body creation is not automatic.
|
|
30
|
+
4. Supply a trusted backend integration with `cb_whoami` and `cb_vault_read`. Set `CEREBRO_BASE`, optional `CEREBRO_AUTH_HEADER` (default `X-API-Key`) and `PI_OFICINAS_HUMAN` (default `human`).
|
|
31
|
+
5. From the package directory, run `python3 bin/oficina --equipo /absolute/path/equipo.json abrir worker` inside the reviewed leader Herdr pane. The gate verifies fresh own-identity/SOUL tool results, model/thinking, tools, session and pane ownership. A marker alone grants nothing.
|
|
32
|
+
|
|
33
|
+
The secretary is inactive without a matching explicit body/team. Optional `pi_coding_agent_dir`, `pi_coding_session_dir`, `relanzar_modo` remain schema fields. A mini-helper may share only its own parent’s identity by body-local opt-in; `PI_OFICINAS_MINI_MODEL` can pin its canonical model. Never inherit another role’s key.
|
|
34
|
+
|
|
35
|
+
## Validation and rollback
|
|
36
|
+
|
|
37
|
+
To remove, `pi remove SOURCE`, then reload/restart your test session; preserve logs, snapshots and sessions.
|
|
38
|
+
|
|
39
|
+
Full monorepo configuration and reproducible validation: [README](https://github.com/otixe/pi-oficinas/blob/main/README.md), [VALIDATION](https://github.com/otixe/pi-oficinas/blob/main/VALIDATION.md).
|
|
40
|
+
|
|
41
|
+
## Security & limits
|
|
42
|
+
|
|
43
|
+
Review extension code before installation: it runs with process permissions, **not inside a sandbox**. Opening gates do not replace backend authorization or OS isolation. Body creation is not automatic; neutral profiles are inactive and a marker alone grants nothing. Never inherit another role's key. This package does **not** ship a Cerebro client, credentials or real souls.
|
|
44
|
+
|
|
45
|
+
Validated on Pi 1.4.0; the five host packages are peerDependencies `"*"`, never bundled copies. Wildcard peers are not a promise of compatibility with every host version. Tests use real Pi/Herdr with model/backend doubles and do **not** certify production authentication. Review source/trust before installation.
|
|
46
|
+
|
|
47
|
+
For local archives, extract the tarball and install its `package` directory: Pi 1.4.0 does not load `.tgz` extension sources even when `pi install` succeeds. The literal install exits 0; print/load exits 1. See the monorepo README for the supported extraction command.
|
|
48
|
+
|
|
49
|
+
Registry installation uses `pi install npm:@otixe/pi-oficinas@0.1.0` once the publisher releases that version. npm sources and repository/gallery URLs require separately authorized publication; a local checkout does not make them available.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Fail-closed, secret-free configuration shared by the office Python CLI.
|
|
2
|
+
|
|
3
|
+
The path is explicit (--equipo), inherited (PI_OFICINAS_EQUIPO), or local to
|
|
4
|
+
this package. Never resolve a missing file against the caller's cwd.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import pwd
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import stat
|
|
12
|
+
|
|
13
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
14
|
+
# Source checkout owns a canonical legacy config; a vendored team package owns
|
|
15
|
+
# only its parent's config. Neither case selects a foreign cwd silently.
|
|
16
|
+
DEFAULT_PATH = ROOT / 'equipo.json' if (ROOT / 'equipo.json').is_file() else ROOT.parent / 'equipo.json'
|
|
17
|
+
FIELDS = frozenset(('version', 'lider', 'lider_key_env', 'office_key_prefix',
|
|
18
|
+
'base', 'lider_body', 'oficinas', 'distribucion', 'soul_prefix'))
|
|
19
|
+
OPTIONAL_FIELDS = frozenset(('souls', 'secretaria', 'pi_bin', 'relanzar_modo',
|
|
20
|
+
'pi_coding_agent_dir', 'pi_coding_session_dir'))
|
|
21
|
+
SECRETARIA_BOOL_FIELDS = frozenset(('monitor', 'avisos', 'contexto', 'correo', 'correo_respaldo'))
|
|
22
|
+
SECRETARIA_RANGES = {'quiet_min': (1, 1440), 'parada_min': (1, 1440), 'pegada_min': (1, 1440),
|
|
23
|
+
'reentrada_cooldown_min': (60, 1440), 'contexto_aviso': (1, 99),
|
|
24
|
+
'contexto_ttl_min': (1, 1440), 'gracia_min': (0, 60), 'correo_cada_min': (1, 1440),
|
|
25
|
+
'correo_tope_diario': (1, 1000), 'correo_tope_hora': (1, 100), 'confirmacion_min': (1, 1440)}
|
|
26
|
+
NAME = re.compile(r'[a-z][a-z0-9-]{0,39}\Z')
|
|
27
|
+
ENV = re.compile(r'[A-Z][A-Z0-9_]*\Z')
|
|
28
|
+
SOUL = re.compile(r'50 Agents/[A-Za-z0-9 -]+(?:/[A-Za-z0-9 -]+)*\Z')
|
|
29
|
+
SOUL_FILE = re.compile(r'50 Agents/[A-Za-z0-9 -]+(?:/[A-Za-z0-9 -]+)*/SOUL\.md\Z')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def soul_path(config, agent):
|
|
33
|
+
return config.get('souls', {}).get(agent) or f"{config['soul_prefix']}/{agent}/Vigente/SOUL.md"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _unique(pairs):
|
|
38
|
+
result = {}
|
|
39
|
+
for key, value in pairs:
|
|
40
|
+
if key in result:
|
|
41
|
+
raise ValueError(f'clave duplicada: {key}')
|
|
42
|
+
result[key] = value
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _real_path(value, *, directory=False):
|
|
47
|
+
if not isinstance(value, str) or not value or '\x00' in value:
|
|
48
|
+
raise ValueError('ruta ausente/inválida')
|
|
49
|
+
path = Path(value)
|
|
50
|
+
if not path.is_absolute() or '..' in path.parts or str(path) != value:
|
|
51
|
+
raise ValueError('ruta debe ser absoluta, normalizada y sin traversal')
|
|
52
|
+
for parent in (path, *path.parents):
|
|
53
|
+
if parent.is_symlink():
|
|
54
|
+
raise ValueError(f'ruta con symlink: {parent}')
|
|
55
|
+
if directory and (not path.is_dir() or not stat.S_ISDIR(path.stat().st_mode)):
|
|
56
|
+
raise ValueError(f'directorio inexistente: {path}')
|
|
57
|
+
return path
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def load_equipo(path=None, *, legacy_anchor=None):
|
|
61
|
+
selected = path if path is not None else os.environ.get('PI_OFICINAS_EQUIPO')
|
|
62
|
+
if not selected:
|
|
63
|
+
raise ValueError('Usar --equipo o PI_OFICINAS_EQUIPO; no hay equipo implícito')
|
|
64
|
+
implicit = path is None and not os.environ.get('PI_OFICINAS_EQUIPO')
|
|
65
|
+
source = _real_path(str(selected))
|
|
66
|
+
if not source.is_file() or not stat.S_ISREG(source.stat().st_mode):
|
|
67
|
+
raise ValueError(f'config de equipo inexistente/no regular: {source}')
|
|
68
|
+
if source.stat().st_uid != os.getuid() or source.stat().st_mode & 0o022:
|
|
69
|
+
raise ValueError('config de equipo no pertenece al usuario o escribible por grupo/otros')
|
|
70
|
+
try:
|
|
71
|
+
data = json.loads(source.read_text(encoding='utf-8'), object_pairs_hook=_unique)
|
|
72
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
73
|
+
raise ValueError(f'config de equipo inválida: {exc}') from exc
|
|
74
|
+
if (not isinstance(data, dict) or not FIELDS.issubset(data) or
|
|
75
|
+
data.keys() - FIELDS - OPTIONAL_FIELDS or
|
|
76
|
+
type(data['version']) is not int or data['version'] != 2):
|
|
77
|
+
raise ValueError('config de equipo: esquema version 2 exacto requerido')
|
|
78
|
+
if 'secretaria' in data:
|
|
79
|
+
secretaria = data['secretaria']
|
|
80
|
+
if (not isinstance(secretaria, dict) or
|
|
81
|
+
set(secretaria) - SECRETARIA_BOOL_FIELDS - SECRETARIA_RANGES.keys() or
|
|
82
|
+
any(type(secretaria.get(key)) is not bool for key in SECRETARIA_BOOL_FIELDS if key in secretaria) or
|
|
83
|
+
any(type(secretaria.get(key)) is not int or
|
|
84
|
+
not SECRETARIA_RANGES[key][0] <= secretaria[key] <= SECRETARIA_RANGES[key][1]
|
|
85
|
+
for key in SECRETARIA_RANGES if key in secretaria)):
|
|
86
|
+
raise ValueError('secretaria inválida: flags booleanos y umbrales dentro de rango')
|
|
87
|
+
lider = data['lider']
|
|
88
|
+
offices = data['oficinas']
|
|
89
|
+
if not isinstance(lider, str) or not NAME.fullmatch(lider):
|
|
90
|
+
raise ValueError('lider inválido')
|
|
91
|
+
if (not isinstance(offices, list) or not offices or len(offices) > 20
|
|
92
|
+
or any(not isinstance(n, str) or not NAME.fullmatch(n) for n in offices)
|
|
93
|
+
or len(set(offices)) != len(offices) or lider in offices):
|
|
94
|
+
raise ValueError('oficinas inválidas, repetidas o incluyen líder')
|
|
95
|
+
for key in ('lider_key_env', 'office_key_prefix'):
|
|
96
|
+
value = data[key]
|
|
97
|
+
if not isinstance(value, str) or not ENV.fullmatch(value):
|
|
98
|
+
raise ValueError(f'{key} inválido')
|
|
99
|
+
if not data['office_key_prefix'].endswith('_'):
|
|
100
|
+
raise ValueError('office_key_prefix debe terminar en _')
|
|
101
|
+
if data['lider_key_env'] in {data['office_key_prefix'] + n.upper().replace('-', '_') for n in offices}:
|
|
102
|
+
raise ValueError('key líder colisiona con variable de oficina')
|
|
103
|
+
soul = data['soul_prefix']
|
|
104
|
+
if not isinstance(soul, str) or not SOUL.fullmatch(soul) or '..' in soul.split('/'):
|
|
105
|
+
raise ValueError('soul_prefix inválido')
|
|
106
|
+
souls = data.get('souls', {})
|
|
107
|
+
if (not isinstance(souls, dict) or
|
|
108
|
+
any(agent not in {lider, *offices} or not isinstance(path, str) or
|
|
109
|
+
not SOUL_FILE.fullmatch(path) or '..' in path.split('/')
|
|
110
|
+
for agent, path in souls.items())):
|
|
111
|
+
raise ValueError('souls inválido: roles propios y rutas dentro del vault terminadas en SOUL.md')
|
|
112
|
+
base = _real_path(data['base'], directory=True)
|
|
113
|
+
body = _real_path(data['lider_body'], directory=True)
|
|
114
|
+
if 'relanzar_modo' in data and data['relanzar_modo'] not in ('fresco', 'retomar'):
|
|
115
|
+
raise ValueError('relanzar_modo debe ser fresco o retomar')
|
|
116
|
+
if 'pi_bin' in data:
|
|
117
|
+
expected_pi_bin = str(base / 'runtime' / 'node_modules' / '.bin' / 'pi')
|
|
118
|
+
if not isinstance(data['pi_bin'], str) or data['pi_bin'] != expected_pi_bin:
|
|
119
|
+
raise ValueError('pi_bin debe ser <base>/runtime/node_modules/.bin/pi; no usar pin global')
|
|
120
|
+
if 'pi_coding_agent_dir' in data and data['pi_coding_agent_dir'] != '.pi-agent':
|
|
121
|
+
raise ValueError('pi_coding_agent_dir debe ser .pi-agent; sin la clave pi usa ~/.pi/agent')
|
|
122
|
+
if 'pi_coding_session_dir' in data and data['pi_coding_session_dir'] != 'sessions':
|
|
123
|
+
raise ValueError('pi_coding_session_dir debe ser sessions bajo cada cuerpo')
|
|
124
|
+
if body == base or not body.is_relative_to(base):
|
|
125
|
+
raise ValueError('lider_body fuera de base')
|
|
126
|
+
distribution = data['distribucion']
|
|
127
|
+
if (not isinstance(distribution, str) or not re.fullmatch(r'[A-Za-z0-9_-]+\.json', distribution)):
|
|
128
|
+
raise ValueError('distribucion debe ser nombre de archivo JSON bajo base')
|
|
129
|
+
_real_path(str(base / distribution))
|
|
130
|
+
_real_path(str(base / 'registro.json'))
|
|
131
|
+
for name in offices:
|
|
132
|
+
_real_path(str(base / name))
|
|
133
|
+
return data
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def paths(config):
|
|
137
|
+
base = Path(config['base'])
|
|
138
|
+
return base, Path(config['lider_body']), base / 'registro.json', base / config['distribucion']
|