agents-city 0.4.0 → 0.5.2
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/.claude-plugin/marketplace.json +1 -1
- package/README.es.md +240 -11
- package/README.md +238 -11
- package/benchmarks/latency/fake-claude-cli.mjs +2 -1
- package/benchmarks/reception/README.md +16 -0
- package/benchmarks/reception/run.mjs +84 -0
- package/bin/agents-city.js +1 -0
- package/bin/connect +5 -0
- package/bin/hall.html +117 -18
- package/bin/navegador.mjs +170 -1
- package/bin/serve.py +201 -101
- package/bin/test +15 -3
- package/bin/test-arnes.py +192 -0
- package/bin/test-cage.py +9 -0
- package/bin/test-channel.py +178 -12
- package/bin/test-claude-runtime.py +132 -4
- package/bin/test-connect-client.mjs +609 -0
- package/bin/test-connect.py +52 -0
- package/bin/test-contracts.py +214 -0
- package/bin/test-diario.py +185 -0
- package/bin/test-doctor.py +31 -0
- package/bin/test-i18n.py +189 -0
- package/bin/test-navegador.py +61 -0
- package/bin/test-seat.py +78 -20
- package/bin/test-security.py +2 -0
- package/bin/test-serve.py +366 -0
- package/city/web/dist-hall/hall.js +789 -214
- package/city/web/src/bienvenida.ts +14 -13
- package/city/web/src/casa.ts +143 -56
- package/city/web/src/demo.ts +61 -31
- package/city/web/src/es.ts +187 -27
- package/city/web/src/explorador.ts +70 -43
- package/city/web/src/hall.ts +520 -103
- package/city/web/src/idioma.ts +21 -1
- package/city/web/src/motores.ts +48 -1
- package/city/web/src/vista.ts +58 -0
- package/demo/graba.py +5 -10
- package/docs/managed-connect.md +237 -0
- package/docs/security.md +41 -2
- package/package.json +8 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/channel/adapter-prompts.ts +10 -0
- package/plugin/channel/adapter.js +78 -31
- package/plugin/channel/agents_city_hybrid_crypto_bg.wasm +0 -0
- package/plugin/channel/bus.js +109 -65
- package/plugin/channel/bus.ts +11 -1
- package/plugin/channel/client.js +67 -30
- package/plugin/channel/delivery-queue.ts +160 -21
- package/plugin/channel/hub/remote-roads.ts +32 -1
- package/plugin/channel/hub/road-controller.ts +116 -16
- package/plugin/channel/kinsh_vodozemac_wasm_bg.wasm +0 -0
- package/plugin/channel/licenses/CONNECT_CLIENT_THIRD_PARTY_NOTICES.md +21 -0
- package/plugin/channel/licenses/HYBRID_CRYPTO_THIRD_PARTY_NOTICES.md +12 -0
- package/plugin/channel/licenses/hybrid-crypto-Apache-2.0.txt +201 -0
- package/plugin/channel/licenses/keyring-MIT.txt +21 -0
- package/plugin/channel/licenses/vodozemac-Apache-2.0.txt +201 -0
- package/plugin/channel/local-hub.js +2080 -97
- package/plugin/channel/local-hub.ts +5 -4
- package/plugin/channel/managed-connect/bridge.ts +208 -0
- package/plugin/channel/managed-connect/cli.ts +416 -0
- package/plugin/channel/managed-connect/device.ts +15 -0
- package/plugin/channel/managed-connect/local-cities.ts +94 -0
- package/plugin/channel/managed-connect/person-message.ts +74 -0
- package/plugin/channel/managed-connect/reception-bridge.ts +341 -0
- package/plugin/channel/managed-connect/relay-session.ts +7 -0
- package/plugin/channel/managed-connect/storage.ts +653 -0
- package/plugin/channel/managed-connect/transport.ts +87 -0
- package/plugin/channel/managed-connect-cli.js +4777 -0
- package/plugin/channel/managed-connect-cli.ts +7 -0
- package/plugin/channel/managed-connect-client.d.ts +261 -0
- package/plugin/channel/managed-connect-client.js +6572 -0
- package/plugin/channel/managed-connect-client.manifest.json +38 -0
- package/plugin/channel/package-lock.json +123 -105
- package/plugin/channel/package.json +4 -4
- package/plugin/channel/protocol.ts +4 -0
- package/plugin/channel/reception.ts +896 -0
- package/plugin/channel/road-cli.ts +1 -1
- package/plugin/channel/runtime/arnes.json +189 -0
- package/plugin/channel/runtime/arnes.ts +47 -0
- package/plugin/channel/runtime/claude.ts +18 -0
- package/plugin/channel/runtime/codex-config.ts +51 -1
- package/plugin/channel/runtime/codex.ts +31 -11
- package/plugin/channel/runtime/kimi.ts +6 -4
- package/plugin/channel/runtime-files.ts +39 -5
- package/plugin/channel/runtime-gateway.js +426 -100
- package/plugin/channel/runtime-gateway.ts +12 -0
- package/plugin/channel/trust/agents-city-sandbox-roots.json +66 -0
- package/plugin/scripts/arnes.py +271 -0
- package/plugin/scripts/busca.py +46 -10
- package/plugin/scripts/cage.py +5 -0
- package/plugin/scripts/city-session.sh +144 -28
- package/plugin/scripts/crecimiento.py +4 -1
- package/plugin/scripts/demos.py +58 -43
- package/plugin/scripts/desinstala.py +36 -21
- package/plugin/scripts/diario.py +119 -0
- package/plugin/scripts/doctor.py +90 -15
- package/plugin/scripts/read-card.py +48 -8
- package/plugin/scripts/reception.py +665 -0
package/plugin/scripts/doctor.py
CHANGED
|
@@ -22,6 +22,7 @@ import sys
|
|
|
22
22
|
|
|
23
23
|
GUIONES = os.path.dirname(os.path.abspath(__file__))
|
|
24
24
|
sys.path.insert(0, GUIONES)
|
|
25
|
+
import arnes # noqa: E402 what we do to the CLIs somebody already had
|
|
25
26
|
from dataclasses import dataclass
|
|
26
27
|
from typing import Callable
|
|
27
28
|
|
|
@@ -188,15 +189,23 @@ def revisa_entorno():
|
|
|
188
189
|
fuera.append(('python', True, _sys.version.split()[0]))
|
|
189
190
|
# Every one of these spells its version flag differently, and tmux answers
|
|
190
191
|
# `--version` with an error that reads like a working version string.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
#
|
|
193
|
+
# Five independent subprocesses: asked together, the wait is the slowest one
|
|
194
|
+
# rather than the sum of five. A doctor that takes half a second to say
|
|
195
|
+
# everything is fine gets run; one that takes two does not.
|
|
196
|
+
programas = (('tmux', '-V', True), ('bash', '--version', True),
|
|
197
|
+
('git', '--version', True), ('node', '--version', True),
|
|
198
|
+
('gh', '--version', False))
|
|
199
|
+
from concurrent.futures import ThreadPoolExecutor # noqa: PLC0415
|
|
200
|
+
|
|
201
|
+
with ThreadPoolExecutor(max_workers=len(programas)) as piscina:
|
|
202
|
+
versiones = list(piscina.map(lambda p: _version_de(p[0], p[1]), programas))
|
|
203
|
+
for (programa, _bandera, obligatorio), v in zip(programas, versiones, strict=True):
|
|
196
204
|
fuera.append((programa, bool(v) if obligatorio else (True if v else None),
|
|
197
205
|
v or ('missing' if obligatorio else 'not installed (optional)')))
|
|
198
206
|
|
|
199
|
-
|
|
207
|
+
# The declaration owns which runtimes exist and what each is called on disk.
|
|
208
|
+
motores = [e['binario'] for e in arnes.informe() if e['instalado']]
|
|
200
209
|
fuera.append(('runtimes', bool(motores),
|
|
201
210
|
', '.join(motores) if motores else 'none — a city needs at least one CLI'))
|
|
202
211
|
|
|
@@ -257,22 +266,88 @@ def informe_entorno():
|
|
|
257
266
|
return 1 if roto else 0
|
|
258
267
|
|
|
259
268
|
|
|
260
|
-
def
|
|
269
|
+
def _diario(empaqueta, resto):
|
|
270
|
+
"""`doctor --log` reads the journal; `doctor --report` bundles it to send.
|
|
271
|
+
|
|
272
|
+
The report is the thing that was missing. When this breaks on somebody
|
|
273
|
+
else's machine, the useful answer is not "tell me what you did" — it is a
|
|
274
|
+
file they can attach without reading it first. So it carries the journal and
|
|
275
|
+
the environment report, and nothing else: `diario` redacts credentials on
|
|
276
|
+
the way in, so what lands here was never a secret to begin with.
|
|
277
|
+
"""
|
|
278
|
+
import cities # noqa: PLC0415
|
|
279
|
+
import diario # noqa: PLC0415
|
|
280
|
+
|
|
281
|
+
datos = cities.actual()
|
|
282
|
+
try:
|
|
283
|
+
cuantas = int(resto[0]) if resto else (2000 if empaqueta else 60)
|
|
284
|
+
except ValueError:
|
|
285
|
+
cuantas = 60
|
|
286
|
+
lineas = diario.lee(datos, cuantas)
|
|
287
|
+
if not empaqueta:
|
|
288
|
+
print(f'\n {diario.ruta(datos)}\n')
|
|
289
|
+
if not lineas:
|
|
290
|
+
print(' Nothing recorded yet. The Hall writes here as you use it.\n')
|
|
291
|
+
return 0
|
|
292
|
+
for l in lineas:
|
|
293
|
+
resto_l = {k: v for k, v in l.items() if k not in ('t', 'tipo')}
|
|
294
|
+
detalle = json.dumps(resto_l, ensure_ascii=False)[:160]
|
|
295
|
+
print(f" {l.get('t', '')} {l.get('tipo', ''):8} {detalle}")
|
|
296
|
+
print()
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
import io # noqa: PLC0415
|
|
300
|
+
from contextlib import redirect_stdout # noqa: PLC0415
|
|
301
|
+
|
|
302
|
+
entorno = io.StringIO()
|
|
303
|
+
with redirect_stdout(entorno):
|
|
304
|
+
informe_entorno()
|
|
305
|
+
destino = os.path.join(os.path.expanduser('~'), 'agents-city-report.txt')
|
|
306
|
+
with open(destino, 'w', encoding='utf-8') as f:
|
|
307
|
+
f.write('# agents-city report\n\n')
|
|
308
|
+
f.write('## this machine\n')
|
|
309
|
+
f.write(entorno.getvalue())
|
|
310
|
+
f.write(f'\n## the journal ({len(lineas)} entries)\n\n')
|
|
311
|
+
for l in lineas:
|
|
312
|
+
f.write(json.dumps(l, ensure_ascii=False) + '\n')
|
|
313
|
+
print(f'\n Written to {destino}\n')
|
|
314
|
+
print(' It holds what this machine is and what the town hall did.')
|
|
315
|
+
print(' Credentials are stripped as they are recorded, so it is safe to attach.\n')
|
|
316
|
+
return 0
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def main(argv=None):
|
|
320
|
+
# The arguments are a parameter so this door can be knocked on from a test
|
|
321
|
+
# without a subprocess: `doctor --config` is the command that backs a claim
|
|
322
|
+
# about somebody's machine, and it deserves to be checked like one.
|
|
323
|
+
argv = list(sys.argv if argv is None else argv)
|
|
261
324
|
# `doctor` with no file is the environment report — what a person means by
|
|
262
325
|
# the word. The config migration keeps its own path, unchanged.
|
|
263
|
-
if len(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
326
|
+
if len(argv) > 1 and argv[1] in ('-h', '--help', 'help'):
|
|
327
|
+
print(
|
|
328
|
+
' usage: agents-city doctor [--config] [config.json [--fix]]\n\n'
|
|
329
|
+
' With no arguments: check this machine — tools, runtimes, the\n'
|
|
330
|
+
' cage, the selected city, and whether a newer version is out.\n'
|
|
331
|
+
' --config: what this product does to the CLIs you already have —\n'
|
|
332
|
+
' what it adds, what it inherits from your own settings, and what\n'
|
|
333
|
+
' it leaves alone. Add --json for the same thing as data.\n'
|
|
334
|
+
' With a config file: detect, explain and migrate an old shape.'
|
|
335
|
+
)
|
|
336
|
+
return 0
|
|
337
|
+
if len(argv) == 1:
|
|
270
338
|
return informe_entorno()
|
|
339
|
+
# The claim this product makes about somebody else's machine, printed so it
|
|
340
|
+
# can be checked instead of believed.
|
|
341
|
+
if argv[1] in ('--config', 'config'):
|
|
342
|
+
return arnes.main(argv[2:])
|
|
343
|
+
# What happened, and something to attach to an issue.
|
|
344
|
+
if argv[1] in ('--log', 'log', '--report', 'report'):
|
|
345
|
+
return _diario(argv[1] in ('--report', 'report'), argv[2:])
|
|
271
346
|
p = argparse.ArgumentParser(description='Detect, explain and migrate an old config shape.')
|
|
272
347
|
p.add_argument('fichero', help='the config JSON file to check')
|
|
273
348
|
p.add_argument('--fix', action='store_true', help='rewrite (default is dry-run report)')
|
|
274
349
|
p.add_argument('--stamp', default='manual', help='backup suffix (tests pass a fixed value)')
|
|
275
|
-
args = p.parse_args()
|
|
350
|
+
args = p.parse_args(argv[1:])
|
|
276
351
|
try:
|
|
277
352
|
reporte = cura_fichero(args.fichero, args.stamp, dry_run=not args.fix)
|
|
278
353
|
except (OSError, json.JSONDecodeError) as e:
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
read-card.py <card.md> repos -> comma-separated list
|
|
6
6
|
read-card.py --window <repo> -> canonical tmux/bus actor slug
|
|
7
7
|
read-card.py --actor-role <card.md> <actor> -> safe operating role or blank
|
|
8
|
+
read-card.py --varios <card.md> f1 f2 ... -> one line per field, in order
|
|
9
|
+
read-card.py --ventana <card.md> <window> -> everything the launcher needs
|
|
10
|
+
for that window, one per line
|
|
8
11
|
|
|
9
12
|
A thin front on card.py, which is where the parsing lives. It used to hold its own
|
|
10
13
|
copy, and that copy is why the tmux session asked for `agente` — a field no card in
|
|
@@ -19,6 +22,16 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
19
22
|
import card # noqa: E402
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
def _campo(texto, campo):
|
|
26
|
+
valor = card.campo(texto, campo)
|
|
27
|
+
# A bracketed value is a list everywhere else in this product — `repos:`,
|
|
28
|
+
# `agents:`, `mounts.<agent>:` — and the shell wants one line. Deciding by
|
|
29
|
+
# SHAPE rather than by a list of blessed field names is what stopped this
|
|
30
|
+
# from having to be edited every time the card grew another list.
|
|
31
|
+
esLista = valor.strip().startswith('[') and valor.strip().endswith(']')
|
|
32
|
+
return ','.join(card.lista(valor)) if esLista else valor
|
|
33
|
+
|
|
34
|
+
|
|
22
35
|
def main():
|
|
23
36
|
if len(sys.argv) == 3 and sys.argv[1] == '--window':
|
|
24
37
|
print(card.ventana(sys.argv[2]))
|
|
@@ -31,10 +44,43 @@ def main():
|
|
|
31
44
|
actor = card.ventana(sys.argv[3])
|
|
32
45
|
print(card.rol_seguro(card.campo(texto, f'role.{actor}')))
|
|
33
46
|
return
|
|
47
|
+
# Everything one window needs, in one interpreter.
|
|
48
|
+
#
|
|
49
|
+
# The launcher used to ask three times per window — the operating role, what
|
|
50
|
+
# runs there, and the engine — each a fresh Python start re-reading the same
|
|
51
|
+
# small card. Six lines, in a fixed order, because the shell reads them with
|
|
52
|
+
# `sed -n Np` and a named format would mean parsing on that side too:
|
|
53
|
+
#
|
|
54
|
+
# 1 role 2 runs 3 model.<window> 4 model 5 effort.<window> 6 effort
|
|
55
|
+
if len(sys.argv) == 4 and sys.argv[1] == '--ventana':
|
|
56
|
+
try:
|
|
57
|
+
with open(sys.argv[2], encoding='utf-8') as f:
|
|
58
|
+
texto = f.read()
|
|
59
|
+
except OSError as e:
|
|
60
|
+
sys.exit(f'cannot read {sys.argv[2]}: {e}')
|
|
61
|
+
win = card.ventana(sys.argv[3])
|
|
62
|
+
print(card.rol_seguro(card.campo(texto, f'role.{win}')))
|
|
63
|
+
for campo in (f'runs.{win}', f'model.{win}', 'model', f'effort.{win}', 'effort'):
|
|
64
|
+
print(_campo(texto, campo))
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# Several fields, one interpreter. The launcher asks for a window's model
|
|
68
|
+
# and effort — with the card default behind each — which was four processes
|
|
69
|
+
# re-parsing the same small file, per window, before tmux even attached.
|
|
70
|
+
if len(sys.argv) > 3 and sys.argv[1] == '--varios':
|
|
71
|
+
try:
|
|
72
|
+
with open(sys.argv[2], encoding='utf-8') as f:
|
|
73
|
+
texto = f.read()
|
|
74
|
+
except OSError as e:
|
|
75
|
+
sys.exit(f'cannot read {sys.argv[2]}: {e}')
|
|
76
|
+
for campo in sys.argv[3:]:
|
|
77
|
+
print(_campo(texto, campo))
|
|
78
|
+
return
|
|
34
79
|
if len(sys.argv) < 3:
|
|
35
80
|
sys.exit(
|
|
36
81
|
'usage: read-card.py <card.md> <field> | --window <repo> | '
|
|
37
|
-
'--actor-role <card.md> <actor>'
|
|
82
|
+
'--actor-role <card.md> <actor> | --varios <card.md> <field>... | '
|
|
83
|
+
'--ventana <card.md> <window>'
|
|
38
84
|
)
|
|
39
85
|
ruta, campo = sys.argv[1], sys.argv[2]
|
|
40
86
|
try:
|
|
@@ -42,13 +88,7 @@ def main():
|
|
|
42
88
|
texto = f.read()
|
|
43
89
|
except OSError as e:
|
|
44
90
|
sys.exit(f'cannot read {ruta}: {e}')
|
|
45
|
-
|
|
46
|
-
# A bracketed value is a list everywhere else in this product — `repos:`,
|
|
47
|
-
# `agents:`, `mounts.<agent>:` — and the shell wants one line. Deciding by
|
|
48
|
-
# SHAPE rather than by a list of blessed field names is what stopped this
|
|
49
|
-
# from having to be edited every time the card grew another list.
|
|
50
|
-
esLista = valor.strip().startswith('[') and valor.strip().endswith(']')
|
|
51
|
-
print(','.join(card.lista(valor)) if esLista else valor)
|
|
91
|
+
print(_campo(texto, campo))
|
|
52
92
|
|
|
53
93
|
|
|
54
94
|
if __name__ == '__main__':
|