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
|
@@ -72,6 +72,7 @@ function runNext() {
|
|
|
72
72
|
working = true;
|
|
73
73
|
const message = queue.shift();
|
|
74
74
|
const text = userText(message);
|
|
75
|
+
const delay = behavior() === 'slow' ? 350 : 5;
|
|
75
76
|
setTimeout(() => {
|
|
76
77
|
emit({
|
|
77
78
|
type: 'assistant',
|
|
@@ -95,7 +96,7 @@ function runNext() {
|
|
|
95
96
|
working = false;
|
|
96
97
|
runNext();
|
|
97
98
|
}, 5);
|
|
98
|
-
},
|
|
99
|
+
}, delay);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
function userText(message) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Local reception ingest
|
|
2
|
+
|
|
3
|
+
This measures the durable, human-quarantine boundary only: decrypted messages
|
|
4
|
+
committed to private local SQLite in protocol-v4 batches. It uses no network,
|
|
5
|
+
relay, model, or fake model latency.
|
|
6
|
+
|
|
7
|
+
Build the public client, then run the 1,000-message/second gate:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm --prefix plugin/channel run build
|
|
11
|
+
node benchmarks/reception/run.mjs --messages 1000 --batch-size 32 --minimum 1000
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Passing this gate says the computer can quarantine the offered transport load
|
|
15
|
+
without loss. It does not say a human or model can answer 1,000 requests per
|
|
16
|
+
second. Pending-count and byte caps apply backpressure when review falls behind.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Reproducible local reception-ingest capacity check. No network or model. */
|
|
3
|
+
|
|
4
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
8
|
+
import {
|
|
9
|
+
receptionDatabasePath,
|
|
10
|
+
recordReceptionMessages,
|
|
11
|
+
} from '../../plugin/channel/managed-connect-client.js';
|
|
12
|
+
|
|
13
|
+
const integerArg = (name, fallback, minimum, maximum) => {
|
|
14
|
+
const index = process.argv.indexOf(name);
|
|
15
|
+
const value = index >= 0 ? Number(process.argv[index + 1]) : fallback;
|
|
16
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
17
|
+
throw new Error(`${name} must be an integer from ${minimum} to ${maximum}`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const total = integerArg('--messages', 1_000, 1, 100_000);
|
|
23
|
+
const batchSize = integerArg('--batch-size', 32, 1, 32);
|
|
24
|
+
const minimum = integerArg('--minimum', 1_000, 1, 1_000_000);
|
|
25
|
+
const root = mkdtempSync(join(tmpdir(), 'agents-city-reception-bench-'));
|
|
26
|
+
const context = {
|
|
27
|
+
dataDir: root,
|
|
28
|
+
appHome: root,
|
|
29
|
+
runtimeDir: join(root, '.runtime', 'bus', 'city-target'),
|
|
30
|
+
owner: 'owner',
|
|
31
|
+
city: { id: 'city_target', address: 'owner/target', name: 'Target' },
|
|
32
|
+
domain: 'custom',
|
|
33
|
+
seatRole: '',
|
|
34
|
+
actors: { seat: { role: 'chair' } },
|
|
35
|
+
engines: { seat: 'claude' },
|
|
36
|
+
roads: [],
|
|
37
|
+
};
|
|
38
|
+
const envelope = (index) => ({
|
|
39
|
+
protocol: 'agents-city-bus/2',
|
|
40
|
+
id: `managed_bench_${String(index).padStart(8, '0')}`,
|
|
41
|
+
kind: 'road.message',
|
|
42
|
+
scope: 'road',
|
|
43
|
+
thread: null,
|
|
44
|
+
from: { city: 'peer/source', actor: 'seat', role: 'external-seat' },
|
|
45
|
+
to: { city: context.city.address, actor: 'seat' },
|
|
46
|
+
createdAt: '2026-08-28T12:00:00.000Z',
|
|
47
|
+
payload: {
|
|
48
|
+
text: 'x'.repeat(256),
|
|
49
|
+
transport: 'managed-e2ee',
|
|
50
|
+
roadId: 'road_benchmark',
|
|
51
|
+
remoteMessageId: String(index),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
// Exclude schema creation and first WAL setup from steady ingest.
|
|
57
|
+
recordReceptionMessages(context, [envelope(total)]);
|
|
58
|
+
const messages = Array.from({ length: total }, (_, index) => envelope(index));
|
|
59
|
+
const started = performance.now();
|
|
60
|
+
for (let index = 0; index < messages.length; index += batchSize) {
|
|
61
|
+
recordReceptionMessages(context, messages.slice(index, index + batchSize));
|
|
62
|
+
}
|
|
63
|
+
const seconds = (performance.now() - started) / 1_000;
|
|
64
|
+
const database = new DatabaseSync(receptionDatabasePath(root), { readOnly: true });
|
|
65
|
+
const persisted = Number(
|
|
66
|
+
database.prepare('SELECT COUNT(*) AS count FROM reception_messages').get().count,
|
|
67
|
+
) - 1;
|
|
68
|
+
database.close();
|
|
69
|
+
const messagesPerSecond = total / seconds;
|
|
70
|
+
const result = {
|
|
71
|
+
messages: total,
|
|
72
|
+
batchSize,
|
|
73
|
+
persisted,
|
|
74
|
+
lost: total - persisted,
|
|
75
|
+
seconds: Number(seconds.toFixed(3)),
|
|
76
|
+
messagesPerSecond: Number(messagesPerSecond.toFixed(1)),
|
|
77
|
+
minimum,
|
|
78
|
+
passed: persisted === total && messagesPerSecond >= minimum,
|
|
79
|
+
};
|
|
80
|
+
console.log(JSON.stringify(result, null, 2));
|
|
81
|
+
if (!result.passed) process.exitCode = 1;
|
|
82
|
+
} finally {
|
|
83
|
+
rmSync(root, { recursive: true, force: true });
|
|
84
|
+
}
|
package/bin/agents-city.js
CHANGED
|
@@ -25,6 +25,7 @@ const ORDENES = {
|
|
|
25
25
|
cities: { que: ['bin/cities'], di: 'list, create or select your cities' },
|
|
26
26
|
agents: { que: ['bin/agents'], di: 'list agents and manage their workspace mounts' },
|
|
27
27
|
road: { que: ['bin/road'], di: 'connect two cities explicitly' },
|
|
28
|
+
connect: { que: ['bin/connect'], di: 'pair this computer and open encrypted managed Roads' },
|
|
28
29
|
bus: { que: ['bin/bus'], di: 'send information over those roads (seat only)' },
|
|
29
30
|
committee: { que: ['bin/committee'], di: 'chair-mediated work with repo agents' },
|
|
30
31
|
benchmark: { que: ['bin/benchmark'], di: 'offline stress/governance or opt-in live runtime latency' },
|
package/bin/connect
ADDED
package/bin/hall.html
CHANGED
|
@@ -79,11 +79,6 @@ button,input,select{font:inherit;color:inherit}
|
|
|
79
79
|
fallback keeps today's behaviour until somebody grabs it. */
|
|
80
80
|
#app{display:grid;grid-template-columns:250px minmax(420px,1fr) var(--live-ancho,minmax(420px,500px));height:100%}
|
|
81
81
|
@media(max-width:1180px){#app{grid-template-columns:210px minmax(360px,1fr) var(--live-ancho,390px)}}
|
|
82
|
-
@media(max-width:900px){#app{grid-template-columns:minmax(0,1fr) var(--live-ancho,320px);grid-template-rows:auto 1fr}
|
|
83
|
-
nav{grid-column:1/-1;border-right:none;border-bottom:1px solid var(--linea);padding:10px 0}
|
|
84
|
-
nav .marca,nav .pie{display:none}nav ol{display:flex;overflow-x:auto;gap:4px}}
|
|
85
|
-
@media(max-width:680px){#app{grid-template-columns:1fr;grid-template-rows:auto 1fr}
|
|
86
|
-
#livePanel{display:none}}
|
|
87
82
|
|
|
88
83
|
/* ── rail ───────────────────────────────────────────────────────────────── */
|
|
89
84
|
nav{background:var(--suelo2);border-right:1px solid var(--linea);padding:20px 0 16px;
|
|
@@ -241,6 +236,79 @@ input[type=text]:focus,select:focus{outline:none;border-color:var(--lampara)}
|
|
|
241
236
|
.dos{display:grid;grid-template-columns:1fr 1fr;gap:13px}
|
|
242
237
|
@media(max-width:640px){.dos{grid-template-columns:1fr}}
|
|
243
238
|
|
|
239
|
+
/* owner reception: remote text is displayed, never interpreted by the page */
|
|
240
|
+
.recModo{display:flex;align-items:center;justify-content:space-between;gap:18px;
|
|
241
|
+
padding:13px 15px;background:var(--suelo2);border:1px solid var(--linea);border-radius:5px}
|
|
242
|
+
.recModo>div{display:grid;grid-template-columns:auto 1fr;align-items:baseline;gap:4px 10px}
|
|
243
|
+
.recModo .et2{font:600 9px var(--hud);letter-spacing:.12em;text-transform:uppercase;
|
|
244
|
+
color:var(--tinta3)}
|
|
245
|
+
.recModo b{font:600 13px var(--hud);color:var(--tinta)}
|
|
246
|
+
.recModo p{grid-column:1/-1;margin:0;color:var(--tinta3);font-size:12px}
|
|
247
|
+
.recAutoConfig{margin:8px 0 18px;padding:14px;background:var(--suelo2);border:1px solid var(--linea);
|
|
248
|
+
border-radius:6px;display:grid;gap:13px}.recAutoConfig fieldset{display:flex;gap:16px;border:0;
|
|
249
|
+
padding:0;margin:0}.recAutoConfig legend{font:600 9px var(--hud);letter-spacing:.1em;
|
|
250
|
+
text-transform:uppercase;color:var(--tinta3);margin-bottom:7px}.recAutoConfig fieldset label{
|
|
251
|
+
font:11px var(--hud)}.recAutoConfig input[type=radio]{accent-color:var(--lampara)}
|
|
252
|
+
.recReglas{display:grid;gap:7px}.recReglas>p,.recAutoAviso{margin:0;color:var(--tinta3);
|
|
253
|
+
font-size:11px}.recReglas label{display:grid;grid-template-columns:minmax(150px,.4fr) minmax(0,1fr);
|
|
254
|
+
gap:10px;align-items:center}.recReglas label span b,.recReglas label span small{display:block}
|
|
255
|
+
.recReglas label span b{font:600 11px var(--hud)}.recReglas label span small{font:9px var(--mono);
|
|
256
|
+
color:var(--tinta3)}.recAutoConfig>button{justify-self:start}
|
|
257
|
+
.recConexiones{margin:18px 0;padding:14px;background:var(--suelo2);border:1px solid var(--linea);
|
|
258
|
+
border-radius:7px}.recSeccion{display:flex;align-items:baseline;justify-content:space-between;
|
|
259
|
+
gap:12px;margin-bottom:10px}.recSeccion b{font:600 13px var(--hud)}
|
|
260
|
+
.recSeccion span{font:10px var(--mono);color:var(--tinta3)}
|
|
261
|
+
.recCompose{display:grid;grid-template-columns:1fr auto;gap:8px;padding:10px;
|
|
262
|
+
background:var(--suelo3);border:1px solid var(--linea);border-radius:5px}
|
|
263
|
+
.recCompose label{display:grid;gap:5px}.recCompose label span{font:600 9.5px var(--hud);
|
|
264
|
+
letter-spacing:.09em;text-transform:uppercase;color:var(--tinta3)}
|
|
265
|
+
.recComposePersona{grid-column:1/-1}.recComposePersona select{width:100%;font:12px var(--hud)}
|
|
266
|
+
.recComposeTexto{grid-column:1/-1}.recCompose textarea{width:100%;
|
|
267
|
+
min-height:66px;resize:vertical}.recCompose button{grid-column:2;justify-self:end}
|
|
268
|
+
.recResumen{display:flex;align-items:baseline;gap:9px;padding-bottom:10px;
|
|
269
|
+
border-bottom:1px solid var(--linea)}
|
|
270
|
+
.recResumen b{font:500 25px var(--mono)}
|
|
271
|
+
.recResumen span{font:600 11px var(--hud);text-transform:uppercase;letter-spacing:.08em}
|
|
272
|
+
.recResumen small{margin-left:auto;font:10px var(--mono);color:var(--tinta3)}
|
|
273
|
+
.recMensajes{display:flex;flex-direction:column;gap:12px;margin-top:12px}
|
|
274
|
+
.recMensaje{background:var(--suelo2);border:1px solid var(--linea);border-radius:7px;
|
|
275
|
+
overflow:hidden;box-shadow:var(--sombra)}
|
|
276
|
+
.recMensaje>header{display:flex;align-items:center;justify-content:space-between;gap:12px;
|
|
277
|
+
padding:11px 14px;border-bottom:1px solid var(--linea);font:10px var(--mono);color:var(--tinta3)}
|
|
278
|
+
.recMensaje>header>div{display:flex;align-items:center;gap:9px;min-width:0}
|
|
279
|
+
.recOrigen{color:var(--tinta);font-weight:600;overflow:hidden;text-overflow:ellipsis}
|
|
280
|
+
.recSeguro{color:var(--verde);font:600 9px var(--hud);letter-spacing:.06em;text-transform:uppercase;
|
|
281
|
+
white-space:nowrap}
|
|
282
|
+
.recTexto{margin:0;padding:17px 16px;white-space:pre-wrap;overflow-wrap:anywhere;
|
|
283
|
+
background:var(--suelo3);color:var(--tinta);border:0;font:13px/1.55 var(--mono);
|
|
284
|
+
max-height:280px;overflow-y:auto}
|
|
285
|
+
.recAcciones{display:grid;grid-template-columns:minmax(0,1fr) minmax(190px,.42fr);gap:14px;
|
|
286
|
+
padding:14px;border-top:1px solid var(--linea)}
|
|
287
|
+
.recAcciones form{display:flex;flex-direction:column;align-items:flex-start;gap:10px}
|
|
288
|
+
.recAcciones fieldset{border:0;padding:0;margin:0;width:100%;min-width:0}
|
|
289
|
+
.recAcciones legend,.recAcciones details>summary,.recAcciones form>label{font:600 9.5px var(--hud);
|
|
290
|
+
letter-spacing:.09em;text-transform:uppercase;color:var(--tinta3)}
|
|
291
|
+
.recDestinos{display:grid;grid-template-columns:repeat(auto-fill,minmax(155px,1fr));gap:6px;
|
|
292
|
+
margin-top:7px}
|
|
293
|
+
.recDestino{display:flex;align-items:center;gap:8px;padding:7px 8px;border:1px solid var(--linea);
|
|
294
|
+
border-radius:4px;background:var(--suelo3);cursor:pointer;min-width:0}
|
|
295
|
+
.recDestino:has(input:checked){border-color:var(--lampara);background:var(--burbuja-mia)}
|
|
296
|
+
.recDestino input{accent-color:var(--lampara);flex:none}
|
|
297
|
+
.recDestino span{min-width:0}.recDestino b,.recDestino small{display:block;overflow:hidden;
|
|
298
|
+
text-overflow:ellipsis;white-space:nowrap}
|
|
299
|
+
.recDestino b{font:600 11px var(--hud)}.recDestino small{font:9px var(--mono);color:var(--tinta3)}
|
|
300
|
+
.recAcciones details{padding-top:2px}.recAcciones details>summary{cursor:pointer;color:var(--carta)}
|
|
301
|
+
.recAcciones details form{margin-top:10px}.recAcciones details input{width:100%}
|
|
302
|
+
.recAvisoAccion{display:flex;justify-content:flex-end;padding:12px 14px;border-top:1px solid var(--linea)}
|
|
303
|
+
.recVacia,.recError{padding:24px;border:1px dashed var(--linea2);border-radius:6px;
|
|
304
|
+
color:var(--tinta3);text-align:center}
|
|
305
|
+
.recVacia b{font:600 14px var(--hud);color:var(--tinta)}.recVacia p{margin:5px 0 0}
|
|
306
|
+
.recError{color:var(--carta);text-align:left;font:12px var(--mono)}
|
|
307
|
+
@media(max-width:760px){.recAcciones{grid-template-columns:1fr}.recMensaje>header{align-items:flex-start;
|
|
308
|
+
flex-direction:column}.recResumen{flex-wrap:wrap}.recResumen small{margin-left:0;width:100%}
|
|
309
|
+
.recSeccion,.recCompose label{align-items:flex-start;flex-direction:column}
|
|
310
|
+
.recReglas label{grid-template-columns:1fr}}
|
|
311
|
+
|
|
244
312
|
/* districts + parcels */
|
|
245
313
|
.tr{display:flex;align-items:center;gap:10px;background:var(--suelo2);
|
|
246
314
|
border-bottom:1px solid var(--linea);padding:9px 13px}
|
|
@@ -279,7 +347,7 @@ input[type=text]:focus,select:focus{outline:none;border-color:var(--lampara)}
|
|
|
279
347
|
padding:2px 7px;border-radius:8px;color:#0f172a;font-weight:600}
|
|
280
348
|
.kindChip.code{background:#3b82f6}.kindChip.knowledge{background:#8b5cf6;color:#f4efe5}
|
|
281
349
|
.kindChip.coordinator{background:#f59e0b}
|
|
282
|
-
.rpgRol,.
|
|
350
|
+
.rpgRol,.rpgHogar{font:10.5px var(--mono);color:var(--tinta3)}
|
|
283
351
|
.rpgRun{color:var(--tinta2)}
|
|
284
352
|
.rpgFila{display:grid;grid-template-columns:52px minmax(0,1fr) auto;gap:8px;align-items:center}
|
|
285
353
|
.rpgFila label,.rpgSkills label{font:600 9px var(--hud);letter-spacing:.09em;
|
|
@@ -347,7 +415,6 @@ body.enGuia #app{grid-template-columns:250px minmax(0,1fr)}
|
|
|
347
415
|
.bvOpcion:hover{border-color:var(--lampara)}
|
|
348
416
|
.bvOpcion.elegida{border-color:var(--lampara);background:var(--burbuja-mia);
|
|
349
417
|
box-shadow:inset 3px 0 0 var(--lampara)}
|
|
350
|
-
.bvChips{display:flex;flex-wrap:wrap;gap:6px}
|
|
351
418
|
.bvChip{border:1px solid var(--linea2);background:var(--suelo2);color:var(--tinta2);
|
|
352
419
|
font:11px var(--mono);padding:5px 10px;border-radius:999px;cursor:pointer}
|
|
353
420
|
.bvChip:hover{border-color:var(--lampara)}
|
|
@@ -368,10 +435,6 @@ body.enGuia #app{grid-template-columns:250px minmax(0,1fr)}
|
|
|
368
435
|
.dlg .campo input:focus,.dlg .campo select:focus{outline:none;border-color:var(--lampara)}
|
|
369
436
|
.dlgBotones{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}
|
|
370
437
|
.dlgBotones .bt[disabled]{opacity:.45;cursor:not-allowed}
|
|
371
|
-
.bvClase{font:600 9px var(--mono);letter-spacing:.08em;text-transform:uppercase;
|
|
372
|
-
opacity:.55;font-style:normal;margin-right:6px}
|
|
373
|
-
.bvChip.cl-docs .bvClase{color:var(--verde);opacity:.85}
|
|
374
|
-
.bvChip.cl-worktree .bvClase{color:var(--lampara);opacity:.85}
|
|
375
438
|
.bt.bvMini{padding:7px 12px;font-size:11px;white-space:nowrap}
|
|
376
439
|
.exp{border:1px solid var(--linea2);border-radius:5px;background:var(--suelo);
|
|
377
440
|
padding:8px;margin-bottom:10px}
|
|
@@ -390,8 +453,9 @@ body.enGuia #app{grid-template-columns:250px minmax(0,1fr)}
|
|
|
390
453
|
.expMiga{border:0;background:none;color:var(--tinta2);font:11px var(--mono);
|
|
391
454
|
padding:1px 3px;cursor:pointer;border-radius:3px}
|
|
392
455
|
.expMiga:hover{color:var(--lampara);background:var(--suelo3)}
|
|
393
|
-
.expLista{max-height:
|
|
456
|
+
.expLista{max-height:min(46vh,420px);overflow:auto;border-top:1px solid var(--linea);
|
|
394
457
|
border-bottom:1px solid var(--linea)}
|
|
458
|
+
.expCuenta{margin:0 0 6px;font:10px var(--mono);color:var(--tinta3)}
|
|
395
459
|
.expFila{display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--linea)}
|
|
396
460
|
.expFila:last-child{border-bottom:0}
|
|
397
461
|
.expFila.puesta{background:var(--burbuja-mia)}
|
|
@@ -441,6 +505,7 @@ body.enGuia #app{grid-template-columns:250px minmax(0,1fr)}
|
|
|
441
505
|
margin-bottom:12px}
|
|
442
506
|
.demoBarra i{display:block;height:100%;background:var(--lampara);
|
|
443
507
|
transition:width .35s ease}
|
|
508
|
+
.dlg .expLista{max-height:min(38vh,320px)}
|
|
444
509
|
.demoPlayer .liveLista{max-height:calc(100vh - 300px);border:1px solid var(--linea);
|
|
445
510
|
border-radius:7px;background:var(--panel);padding:16px}
|
|
446
511
|
.bvMotor{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:6px}
|
|
@@ -583,6 +648,39 @@ body.enGuia #app{grid-template-columns:250px minmax(0,1fr)}
|
|
|
583
648
|
letter-spacing:.03em}.liveEvidence[open] summary,.liveFull[open] summary{color:var(--tono)}
|
|
584
649
|
.liveEvidence ul{margin:7px 0 0;padding-left:16px;color:var(--tinta3);font-size:11.5px;line-height:1.45}
|
|
585
650
|
.liveEvidence li+li{margin-top:4px}.liveFull p{margin-top:7px;color:var(--tinta3);font-size:11.5px}
|
|
651
|
+
|
|
652
|
+
/* Keep responsive overrides after the base rail and live-panel rules. Placing
|
|
653
|
+
them before those rules made the desktop declarations win in the cascade,
|
|
654
|
+
leaving the actual Hall content at zero height on a phone. */
|
|
655
|
+
@media(max-width:900px){
|
|
656
|
+
#app{grid-template-columns:minmax(0,1fr) var(--live-ancho,320px);
|
|
657
|
+
grid-template-rows:auto minmax(0,1fr)}
|
|
658
|
+
body.enGuia #app{grid-template-columns:minmax(0,1fr);
|
|
659
|
+
grid-template-rows:auto minmax(0,1fr)}
|
|
660
|
+
nav{grid-column:1/-1;grid-row:1;border-right:0;border-bottom:1px solid var(--linea);
|
|
661
|
+
padding:8px 10px;display:flex;flex-direction:row;align-items:center;gap:8px;
|
|
662
|
+
overflow-x:auto;overflow-y:hidden}
|
|
663
|
+
nav .marca,nav .pie,nav #ciudades,nav #railOtras{display:none}
|
|
664
|
+
.railCiudad{margin:0;min-width:154px;flex:0 0 auto;padding:7px 10px}
|
|
665
|
+
.railCiudad .railEtiqueta,.railCiudad .railDe{display:none}
|
|
666
|
+
nav ol{padding:0;display:flex;flex:1 0 auto;flex-direction:row;gap:4px}
|
|
667
|
+
nav li{padding:8px 9px}
|
|
668
|
+
main{grid-column:1;grid-row:2}
|
|
669
|
+
#livePanel{grid-column:2;grid-row:2}
|
|
670
|
+
.cuerpo{padding:28px 30px 52px}
|
|
671
|
+
}
|
|
672
|
+
@media(max-width:680px){
|
|
673
|
+
#app{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}
|
|
674
|
+
#livePanel{display:none;grid-column:1;grid-row:2}
|
|
675
|
+
main{grid-column:1;grid-row:2}
|
|
676
|
+
.cuerpo{padding:24px 18px 48px}
|
|
677
|
+
.cuerpo>.dentro{gap:20px}
|
|
678
|
+
.recModo{align-items:stretch;flex-direction:column;gap:10px}
|
|
679
|
+
.recModo>button{align-self:flex-start}
|
|
680
|
+
.recAutoConfig fieldset{flex-direction:column;gap:8px}
|
|
681
|
+
.recCompose{grid-template-columns:1fr}
|
|
682
|
+
.recCompose button{grid-column:1;justify-self:stretch}
|
|
683
|
+
}
|
|
586
684
|
</style>
|
|
587
685
|
</head>
|
|
588
686
|
<body>
|
|
@@ -617,19 +715,19 @@ body.enGuia #app{grid-template-columns:250px minmax(0,1fr)}
|
|
|
617
715
|
aria-label="Resize the live panel" title="Drag to resize · double-click to reset"></div>
|
|
618
716
|
<header>
|
|
619
717
|
<div class="liveTop"><span class="liveDot" id="liveDot"></span>
|
|
620
|
-
<h2>City live</h2><span id="liveState">waiting</span></div>
|
|
621
|
-
<p>A moderated conversation over the WebSocket bus. The seat routes each turn;
|
|
718
|
+
<h2 data-i18n>City live</h2><span id="liveState" data-i18n>waiting</span></div>
|
|
719
|
+
<p data-i18n>A moderated conversation over the WebSocket bus. The seat routes each turn;
|
|
622
720
|
private model reasoning is never shown or stored.</p>
|
|
623
721
|
<div class="liveControls">
|
|
624
722
|
<select id="liveFilter" aria-label="Filter live activity by conversation">
|
|
625
|
-
<option value="">all conversations</option>
|
|
723
|
+
<option value="" data-i18n>all conversations</option>
|
|
626
724
|
</select>
|
|
627
|
-
<button id="liveWorkToggle" type="button" aria-pressed="false">show work</button>
|
|
725
|
+
<button id="liveWorkToggle" type="button" aria-pressed="false" data-i18n>show work</button>
|
|
628
726
|
</div>
|
|
629
727
|
<div id="liveContext" hidden></div>
|
|
630
728
|
</header>
|
|
631
729
|
<ol id="liveEvents" aria-live="polite">
|
|
632
|
-
<li class="liveEmpty">Start the city session. Its visible conversation will appear here as it happens.</li>
|
|
730
|
+
<li class="liveEmpty" data-i18n>Start the city session. Its visible conversation will appear here as it happens.</li>
|
|
633
731
|
</ol>
|
|
634
732
|
</aside>
|
|
635
733
|
</div>
|
|
@@ -638,7 +736,8 @@ body.enGuia #app{grid-template-columns:250px minmax(0,1fr)}
|
|
|
638
736
|
<div class="edCaja">
|
|
639
737
|
<div class="edCab"><b id="edTitulo"></b><span id="edLector"></span>
|
|
640
738
|
<button id="edCerrar" type="button" aria-label="Close">×</button></div>
|
|
641
|
-
<textarea id="edTexto"
|
|
739
|
+
<textarea id="edTexto" name="instruction" aria-label="Agent instructions"
|
|
740
|
+
spellcheck="false"></textarea>
|
|
642
741
|
<div class="edPie"><span id="edRuta"></span>
|
|
643
742
|
<button id="edGuardar" type="button" class="bt ppal">Save</button></div>
|
|
644
743
|
</div>
|
package/bin/navegador.mjs
CHANGED
|
@@ -438,6 +438,67 @@ async function main() {
|
|
|
438
438
|
JSON.stringify(alta),
|
|
439
439
|
);
|
|
440
440
|
|
|
441
|
+
// Human reception must scale by people, not by duplicated composers. With
|
|
442
|
+
// thirty connections the review queue still comes first, then one compact
|
|
443
|
+
// composer whose recipient is selected explicitly.
|
|
444
|
+
const reception = await cdp.evalua(`(async () => {
|
|
445
|
+
const ir = [...document.querySelectorAll('nav li')]
|
|
446
|
+
.find(l => /reception|recepci[oó]n/i.test(l.textContent));
|
|
447
|
+
if (!ir) return { falta: 'no reception entry' };
|
|
448
|
+
ir.click();
|
|
449
|
+
for (let i = 0; i < 40; i++) {
|
|
450
|
+
if (document.querySelector('.recMensaje')) break;
|
|
451
|
+
await new Promise(r => setTimeout(r, 250));
|
|
452
|
+
}
|
|
453
|
+
const message = document.querySelector('.recMensaje');
|
|
454
|
+
const compose = document.querySelector('.recConexiones');
|
|
455
|
+
const route = document.querySelector('[data-rec-route] button[type=submit]');
|
|
456
|
+
const rejection = document.querySelector('.recAcciones details');
|
|
457
|
+
return {
|
|
458
|
+
navRole: ir.getAttribute('role'),
|
|
459
|
+
navTabIndex: ir.getAttribute('tabindex'),
|
|
460
|
+
messages: document.querySelectorAll('.recMensaje').length,
|
|
461
|
+
composers: document.querySelectorAll('.recCompose').length,
|
|
462
|
+
recipients: document.querySelectorAll('.recCompose select[name=connection] option').length,
|
|
463
|
+
reviewFirst: !!message && !!compose &&
|
|
464
|
+
!!(message.compareDocumentPosition(compose) & Node.DOCUMENT_POSITION_FOLLOWING),
|
|
465
|
+
route: route?.textContent?.trim() ?? '',
|
|
466
|
+
rejection: rejection?.textContent?.trim() ?? '',
|
|
467
|
+
};
|
|
468
|
+
})()`);
|
|
469
|
+
comprueba(
|
|
470
|
+
'thirty connected people use one recipient picker, not thirty message boxes',
|
|
471
|
+
reception?.navRole === 'button' && reception?.navTabIndex === '0' &&
|
|
472
|
+
reception?.composers === 1 && reception?.recipients === 30,
|
|
473
|
+
JSON.stringify(reception),
|
|
474
|
+
);
|
|
475
|
+
comprueba(
|
|
476
|
+
'pending messages stay above composing and use distinct route/reject language',
|
|
477
|
+
reception?.messages === 1 && reception?.reviewFirst === true &&
|
|
478
|
+
/route|dirigir/i.test(reception?.route ?? '') &&
|
|
479
|
+
/reason|motivo/i.test(reception?.rejection ?? ''),
|
|
480
|
+
JSON.stringify(reception),
|
|
481
|
+
);
|
|
482
|
+
const routed = await cdp.evalua(`(async () => {
|
|
483
|
+
const form = document.querySelector('[data-rec-route]');
|
|
484
|
+
const checkbox = form?.querySelector('input[name=destination]');
|
|
485
|
+
const button = form?.querySelector('button[type=submit]');
|
|
486
|
+
if (!form || !checkbox || !button) return { falta: true };
|
|
487
|
+
checkbox.click();
|
|
488
|
+
if (button.disabled) return { disabled: true };
|
|
489
|
+
button.click();
|
|
490
|
+
for (let i = 0; i < 40; i++) {
|
|
491
|
+
if (!document.querySelector('.recMensaje')) return { removed: true };
|
|
492
|
+
await new Promise(r => setTimeout(r, 250));
|
|
493
|
+
}
|
|
494
|
+
return { removed: false };
|
|
495
|
+
})()`);
|
|
496
|
+
comprueba(
|
|
497
|
+
'choosing a city and routing removes the message from human quarantine',
|
|
498
|
+
routed?.removed === true,
|
|
499
|
+
JSON.stringify(routed),
|
|
500
|
+
);
|
|
501
|
+
|
|
441
502
|
// The demo shelf: three cards, and a player that actually advances.
|
|
442
503
|
const demo = await cdp.evalua(`(async () => {
|
|
443
504
|
const espera = async (sel, n = 40) => {
|
|
@@ -492,6 +553,114 @@ async function main() {
|
|
|
492
553
|
JSON.stringify(demo?.dice),
|
|
493
554
|
);
|
|
494
555
|
|
|
556
|
+
// Building a house for real: type a name, press the button, get a house.
|
|
557
|
+
//
|
|
558
|
+
// The suite used to open this form, check its fields existed and press
|
|
559
|
+
// Escape — so the one thing it is FOR was never exercised. What that hid:
|
|
560
|
+
// the roles list arrives a few hundred milliseconds after the form opens,
|
|
561
|
+
// the form repainted when it did, and the repaint redrew the name field
|
|
562
|
+
// from a state that had never been told what was typed in it. Typing while
|
|
563
|
+
// that answer was in flight lost the name, and then the button said to
|
|
564
|
+
// give it one. Intermittent, which is the worst kind.
|
|
565
|
+
//
|
|
566
|
+
// So this types IMMEDIATELY, before the roles can land, which is exactly
|
|
567
|
+
// what a person does.
|
|
568
|
+
const construida = await cdp.evalua(`(async () => {
|
|
569
|
+
const espera = async (sel, n = 40) => {
|
|
570
|
+
for (let i = 0; i < n; i++) {
|
|
571
|
+
const el = document.querySelector(sel);
|
|
572
|
+
if (el) return el;
|
|
573
|
+
await new Promise(r => setTimeout(r, 250));
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
};
|
|
577
|
+
const ir = [...document.querySelectorAll('nav li')].find(l => /houses|casas/i.test(l.textContent));
|
|
578
|
+
ir?.click();
|
|
579
|
+
await new Promise(r => setTimeout(r, 600));
|
|
580
|
+
document.getElementById('altaAgente')?.click();
|
|
581
|
+
const campo = await espera('.dlgFondo #bvNombre');
|
|
582
|
+
if (!campo) return { falta: true };
|
|
583
|
+
// Straight away: the roles request is still in flight right now.
|
|
584
|
+
campo.value = 'urgencias';
|
|
585
|
+
campo.dispatchEvent(new Event('input', { bubbles: true }));
|
|
586
|
+
// Long enough for that answer to land and do whatever it does.
|
|
587
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
588
|
+
const sobrevive = (document.querySelector('.dlgFondo #bvNombre') || {}).value;
|
|
589
|
+
const boton = document.querySelector('.dlgFondo [data-dlg="si"]');
|
|
590
|
+
boton?.click();
|
|
591
|
+
const leeNombres = () =>
|
|
592
|
+
[...document.querySelectorAll('.fichaRPG h3')].map(h => h.textContent.trim());
|
|
593
|
+
// Wait for the house, not for a guess at how long building takes. A
|
|
594
|
+
// fixed sleep here made this check itself flaky, which is the one thing
|
|
595
|
+
// a check for a flaky bug must not be.
|
|
596
|
+
let nombres = [];
|
|
597
|
+
for (let i = 0; i < 60; i++) {
|
|
598
|
+
nombres = leeNombres();
|
|
599
|
+
if (!document.querySelector('.dlgFondo') && nombres.includes('urgencias')) break;
|
|
600
|
+
await new Promise(r => setTimeout(r, 250));
|
|
601
|
+
}
|
|
602
|
+
return { sobrevive, cerrado: !document.querySelector('.dlgFondo'), nombres };
|
|
603
|
+
})()`);
|
|
604
|
+
comprueba(
|
|
605
|
+
'a name typed while the roles are still loading is still there afterwards',
|
|
606
|
+
construida?.sobrevive === 'urgencias',
|
|
607
|
+
JSON.stringify(construida),
|
|
608
|
+
);
|
|
609
|
+
comprueba(
|
|
610
|
+
'and pressing build actually builds the house',
|
|
611
|
+
!!construida?.cerrado && (construida?.nombres ?? []).includes('urgencias'),
|
|
612
|
+
JSON.stringify(construida),
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
// And the other half: pressing build with nothing in the name.
|
|
616
|
+
//
|
|
617
|
+
// The failure this guards is not "it refuses" — it is refusing SILENTLY, or
|
|
618
|
+
// refusing while closing the dialog, which loses everything the person had
|
|
619
|
+
// already chosen. That is the same complaint from the other side: a form
|
|
620
|
+
// that says no and takes the work with it.
|
|
621
|
+
const vacia = await cdp.evalua(`(async () => {
|
|
622
|
+
const espera = async (sel, n = 40) => {
|
|
623
|
+
for (let i = 0; i < n; i++) {
|
|
624
|
+
const el = document.querySelector(sel);
|
|
625
|
+
if (el) return el;
|
|
626
|
+
await new Promise(r => setTimeout(r, 250));
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
};
|
|
630
|
+
const antes = document.querySelectorAll('.fichaRPG h3').length;
|
|
631
|
+
document.getElementById('altaAgente')?.click();
|
|
632
|
+
const campo = await espera('.dlgFondo #bvNombre');
|
|
633
|
+
if (!campo) return { falta: true };
|
|
634
|
+
// Choose something first: whatever it says, this must not be thrown away.
|
|
635
|
+
document.querySelector('.dlgFondo [data-bv=\"clase\"][data-id=\"knowledge\"]')?.click();
|
|
636
|
+
await new Promise(r => setTimeout(r, 300));
|
|
637
|
+
document.querySelector('.dlgFondo [data-dlg=\"si\"]')?.click();
|
|
638
|
+
await new Promise(r => setTimeout(r, 1200));
|
|
639
|
+
const caja = document.querySelector('.dlgFondo');
|
|
640
|
+
const elegida = !!document.querySelector('.dlgFondo .bvOpcion.elegida[data-id=\"knowledge\"]');
|
|
641
|
+
const aviso = document.querySelector('#aviso,.aviso,.toast');
|
|
642
|
+
const fuera = {
|
|
643
|
+
sigueAbierto: !!caja,
|
|
644
|
+
elegida,
|
|
645
|
+
dice: (aviso?.textContent ?? '').trim().slice(0, 80),
|
|
646
|
+
despues: document.querySelectorAll('.fichaRPG h3').length,
|
|
647
|
+
antes,
|
|
648
|
+
};
|
|
649
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
650
|
+
await new Promise(r => setTimeout(r, 200));
|
|
651
|
+
return fuera;
|
|
652
|
+
})()`);
|
|
653
|
+
comprueba(
|
|
654
|
+
'building with no name refuses without closing the form',
|
|
655
|
+
!!vacia && !vacia.falta && vacia.sigueAbierto === true,
|
|
656
|
+
JSON.stringify(vacia),
|
|
657
|
+
);
|
|
658
|
+
comprueba(
|
|
659
|
+
'and keeps what was already chosen, and builds nothing',
|
|
660
|
+
!!vacia?.elegida && vacia?.despues === vacia?.antes,
|
|
661
|
+
JSON.stringify(vacia),
|
|
662
|
+
);
|
|
663
|
+
|
|
495
664
|
// Spanish and English, on a switch. The READMEs shipped bilingual and the
|
|
496
665
|
// product did not; a language button that does not actually change the page
|
|
497
666
|
// is worse than none at all.
|
|
@@ -534,7 +703,7 @@ async function main() {
|
|
|
534
703
|
} finally {
|
|
535
704
|
proceso.kill('SIGKILL');
|
|
536
705
|
}
|
|
537
|
-
console.log(`\n ${fallos ? `${fallos} failed` : 'browser ok'} —
|
|
706
|
+
console.log(`\n ${fallos ? `${fallos} failed` : 'browser ok'} — 19 checks\n`);
|
|
538
707
|
process.exit(fallos ? 1 : 0);
|
|
539
708
|
}
|
|
540
709
|
|