@mmmbuto/nexuscrew 0.9.29 → 0.9.30
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 +9 -0
- package/docs/LIVE_PROMPT.md +1 -1
- package/docs/NOTIFICATIONS.md +37 -0
- package/frontend/dist/assets/index-BbcjoZS0.js +167 -0
- package/frontend/dist/assets/{index-CywSos2e.css → index-DqCfZFGi.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +91 -10
- package/frontend/dist/version.json +1 -1
- package/lib/audio/groups.js +1 -1
- package/lib/auth/token.js +4 -4
- package/lib/cli/commands.js +59 -12
- package/lib/cli/doctor.js +5 -5
- package/lib/cli/fleet-service.js +7 -7
- package/lib/cli/init.js +27 -27
- package/lib/cli/pidfile.js +1 -1
- package/lib/cli/service.js +6 -6
- package/lib/cli/stable-alias.js +4 -4
- package/lib/cli/url.js +1 -1
- package/lib/config.js +36 -6
- package/lib/files/routes.js +1 -1
- package/lib/fleet/boot.js +2 -2
- package/lib/fleet/builtin.js +15 -15
- package/lib/fleet/causes.js +1 -1
- package/lib/fleet/cell-exec.js +23 -23
- package/lib/fleet/cell-hold.js +6 -6
- package/lib/fleet/cell-lease-server.js +106 -106
- package/lib/fleet/cell-lease.js +21 -21
- package/lib/fleet/definitions.js +10 -10
- package/lib/fleet/identity-authority.js +1 -1
- package/lib/fleet/launch-broker.js +13 -13
- package/lib/fleet/launch.js +15 -15
- package/lib/fleet/lease-client.js +23 -23
- package/lib/fleet/lease-routes.js +3 -3
- package/lib/fleet/lease-verifier.js +22 -22
- package/lib/fleet/managed.js +35 -35
- package/lib/fleet/prompt-delivery.js +13 -13
- package/lib/fleet/routes.js +15 -10
- package/lib/fleet/runtime.js +24 -24
- package/lib/live-host/bridge.js +36 -36
- package/lib/live-host/routes.js +2 -2
- package/lib/live-host/store.js +4 -4
- package/lib/mcp/server.js +14 -14
- package/lib/mcp/tools.js +5 -5
- package/lib/nodes/access-presets.js +258 -0
- package/lib/nodes/commands.js +198 -4
- package/lib/nodes/store.js +152 -9
- package/lib/nodes/tunnel.js +7 -7
- package/lib/notify/ask-answer-service.js +184 -0
- package/lib/notify/ask-receipts.js +197 -0
- package/lib/notify/ask-relay.js +152 -0
- package/lib/notify/asks.js +28 -3
- package/lib/notify/event-feed-acl.js +95 -0
- package/lib/notify/event-feed-asks-routes.js +144 -0
- package/lib/notify/event-feed-client.js +434 -0
- package/lib/notify/event-feed-history.js +115 -0
- package/lib/notify/event-feed-producers.js +175 -0
- package/lib/notify/event-feed-routes.js +254 -0
- package/lib/notify/event-feed.js +328 -0
- package/lib/notify/events.js +1 -1
- package/lib/notify/notifier.js +3 -3
- package/lib/notify/push-relay.js +240 -0
- package/lib/notify/push.js +8 -8
- package/lib/notify/routes.js +62 -23
- package/lib/proxy/federation.js +74 -3
- package/lib/proxy/node-proxy.js +13 -13
- package/lib/proxy/panel-auth.js +4 -4
- package/lib/proxy/panel-proxy.js +2 -2
- package/lib/proxy/resource-acl.js +145 -0
- package/lib/server.js +372 -39
- package/lib/settings/pairing-coordinator.js +1 -1
- package/lib/settings/routes.js +45 -32
- package/lib/update/manager.js +1 -1
- package/lib/update/runner.js +1 -1
- package/lib/voice/transcribe.js +1 -1
- package/lib/ws/bridge.js +3 -3
- package/package.json +1 -1
- package/frontend/dist/assets/index-B_THfiL5.js +0 -167
package/lib/fleet/builtin.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Fleet built-in. FACADE: espone available/status/up/down/engine/boot e
|
|
3
3
|
// isCellSession leggendo le definizioni da ~/.nexuscrew/fleet.json tramite
|
|
4
4
|
// lib/fleet/definitions.js, insieme a define*/edit*/remove* +
|
|
5
|
-
// schema + capabilities
|
|
5
|
+
// schema + capabilities.
|
|
6
6
|
//
|
|
7
7
|
// La responsabilita' runtime/launch (status/up/down/restart + launch/readiness
|
|
8
8
|
// helpers) e' stata estratta in modo behavior-preserving in:
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
// Agnostico: non conosce "claude"/"glm"/"codex" — lancia solo command+args
|
|
17
17
|
// dichiarati.
|
|
18
18
|
//
|
|
19
|
-
//
|
|
19
|
+
// Security model — unchanged, see launch.js/runtime.js:
|
|
20
20
|
// - up esegue validateCommandTrust(command) e resolveCwd(cwd) PRIMA di lanciare;
|
|
21
21
|
// una cella/engine mancante o non trusted -> httpError(400), NON lancia nulla.
|
|
22
22
|
// - command/args/env NON passano per una shell: execFile + argv diretto.
|
|
23
23
|
// - env: il builtin lancia con un env MINIMALE controllato dal service; engine.env
|
|
24
24
|
// raggiunge il client solo tramite un broker AF_UNIX privato e monouso.
|
|
25
|
-
// - READONLY (cfg.readonlyDefault===true | NEXUSCREW_READONLY=1)
|
|
26
|
-
//
|
|
27
|
-
// - promptMode 'send-keys'
|
|
28
|
-
//
|
|
25
|
+
// - READONLY (cfg.readonlyDefault===true | NEXUSCREW_READONLY=1) blocks every
|
|
26
|
+
// fleet mutation and every up: only status/schema/capabilities pass.
|
|
27
|
+
// - promptMode 'send-keys' injects via bracketed paste; when the command already
|
|
28
|
+
// exited (dead session) it does not type.
|
|
29
29
|
const fs = require('node:fs');
|
|
30
30
|
const os = require('node:os');
|
|
31
31
|
const path = require('node:path');
|
|
@@ -125,7 +125,7 @@ function backfillShellEngine(defsPath, defs, log) {
|
|
|
125
125
|
return esito || defs;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
//
|
|
128
|
+
// Platform-aware backfill of the primary agy engine (idempotent, non-destructive).
|
|
129
129
|
// esistenti su Linux/macOS non-Termux ricevono agy.native in modo idempotente e
|
|
130
130
|
// non distruttivo. Su altre piattaforme (Termux/Windows) e' un no-op: agy non e'
|
|
131
131
|
// un client primario supportato li' e l'utente puo comunque usarlo via shell.local
|
|
@@ -453,7 +453,7 @@ function applyCellTransition(target, engineId, { model, hasModel, policy, hasPol
|
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
// ---------------------------------------------------------------------------
|
|
456
|
-
// cwd
|
|
456
|
+
// Portable cwd: resolveCellCwd reconciles the absolute cwd and cwdRel before every write.
|
|
457
457
|
// cwdRel (home-relative) in una COPPIA COERENTE prima di ogni scrittura:
|
|
458
458
|
// - solo cwd (assoluta valida) -> deriva cwdRel
|
|
459
459
|
// - solo cwdRel (valida) -> calcola la cwd assoluta target
|
|
@@ -582,7 +582,7 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
582
582
|
const off = {
|
|
583
583
|
available: false, provider: 'builtin',
|
|
584
584
|
isCellSession: () => false, capabilities: () => [],
|
|
585
|
-
//
|
|
585
|
+
// Close sempre sicura su qualunque Fleet restituita, anche unavailable:
|
|
586
586
|
// il caller non deve distinguere `off` dalla Fleet reale.
|
|
587
587
|
close: async () => {},
|
|
588
588
|
};
|
|
@@ -609,7 +609,7 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
609
609
|
// early return segue la creazione del manager, e la creazione avviene dopo tutti i
|
|
610
610
|
// gate noti. Manternerla richiede che ogni nuovo gate resti qui sopra.
|
|
611
611
|
|
|
612
|
-
// Bootstrap: fleet.json
|
|
612
|
+
// Bootstrap: a valid fleet.json? Garbage -> unavailable (fail-closed).
|
|
613
613
|
// ENOENT (missing) o parse invalido -> off (unavailable legittimo, fail-closed);
|
|
614
614
|
// EACCES/ELOOP sul lstat (file presente ma non verificabile) -> off CON reason,
|
|
615
615
|
// perche' "non ho potuto guardare" e' un problema diverso da "non c'e'" o "e'
|
|
@@ -733,8 +733,8 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
733
733
|
identityAuthority,
|
|
734
734
|
identityDaemonCredential: cfg.identityDaemonCredential,
|
|
735
735
|
identityLauncherCredential: cfg.identityLauncherCredential,
|
|
736
|
-
//
|
|
737
|
-
//
|
|
736
|
+
// The one-shot broker connection stays OPEN after the payload and becomes
|
|
737
|
+
// the supervisor lease channel.
|
|
738
738
|
...(leaseManager ? {
|
|
739
739
|
onLease: (socket, lease) => {
|
|
740
740
|
// da revisione interna: attachInitial restituisce false se la
|
|
@@ -1363,7 +1363,7 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1363
1363
|
return credentialStatus();
|
|
1364
1364
|
}
|
|
1365
1365
|
|
|
1366
|
-
// schema() —
|
|
1366
|
+
// schema() — field descriptor for the schema-driven UI editor.
|
|
1367
1367
|
function schema() {
|
|
1368
1368
|
return {
|
|
1369
1369
|
schemaVersion: 1,
|
|
@@ -1482,8 +1482,8 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1482
1482
|
schema, definitions, capabilities,
|
|
1483
1483
|
credentialStatus, setLocalCredential, removeLocalCredential, testModel,
|
|
1484
1484
|
defineModel, removeModel, close,
|
|
1485
|
-
// Fetta 2b
|
|
1486
|
-
// MCP delle celle consuma per register/refresh/recovery
|
|
1485
|
+
// Fetta 2b: il lease Live esposto alle route /api/lease, che il bridge
|
|
1486
|
+
// MCP delle celle consuma per register/refresh/recovery. Assente se il
|
|
1487
1487
|
// lease e' disattivato: le route rispondono 501, non 500.
|
|
1488
1488
|
...(leaseManager ? { lease: leaseManager } : {}),
|
|
1489
1489
|
...(identityAuthority ? { identityAuthority } : {}),
|
package/lib/fleet/causes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Cause-preserving diagnostics for Fleet up
|
|
3
|
+
// Cause-preserving diagnostics for Fleet up failures.
|
|
4
4
|
//
|
|
5
5
|
// The up() path of the built-in fleet crosses five boundaries:
|
|
6
6
|
// 1. preflight gates (command trust, cwd, managed-engine config)
|
package/lib/fleet/cell-exec.js
CHANGED
|
@@ -101,7 +101,7 @@ function validIdentity(value) {
|
|
|
101
101
|
function validLease(value) {
|
|
102
102
|
if (value === undefined) return true;
|
|
103
103
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
104
|
-
// 2b
|
|
104
|
+
// 2b: la capability statica e' revocata e non esiste piu' nel payload.
|
|
105
105
|
// Il proof supervisore NON transita qui: arriva sul canale lease dal server.
|
|
106
106
|
if (Object.keys(value).some((k) => !['cellId', 'launchEpoch', 'stablePath'].includes(k))) return false;
|
|
107
107
|
return (value.cellId === undefined || (typeof value.cellId === 'string' && value.cellId.length > 0 && value.cellId.length <= 128))
|
|
@@ -134,8 +134,8 @@ function receivePayload(socketPath, nonce, timeoutMs = 5000, opts = {}) {
|
|
|
134
134
|
const finish = (error, payload) => {
|
|
135
135
|
if (done) return; done = true;
|
|
136
136
|
if (error) { socket.destroy(); reject(error); return; }
|
|
137
|
-
//
|
|
138
|
-
//
|
|
137
|
+
// Opt-in keepOpen: the caller receives the OPEN socket (lease broker)
|
|
138
|
+
// and passes it to the lease-client. Default: destroy (one-shot).
|
|
139
139
|
if (opts.keepOpen) { resolve({ payload, socket }); return; }
|
|
140
140
|
socket.destroy(); resolve(payload);
|
|
141
141
|
};
|
|
@@ -197,7 +197,7 @@ function validIdentityRequest(msg) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
// schema chiuso del metodo verify (v1). Il proof viene VERIFICATO
|
|
200
|
-
//
|
|
200
|
+
// Online dall'authority: il daemon non valuta nulla da solo.
|
|
201
201
|
function validVerifyRequest(msg, channelGeneration) {
|
|
202
202
|
if (msg.method !== 'nexuscrew/identity/verify') return false;
|
|
203
203
|
const params = msg.params;
|
|
@@ -272,7 +272,7 @@ function createIdentityChannel({
|
|
|
272
272
|
const reason = outcome && typeof outcome.reason === 'string' ? outcome.reason : 'identity-unverified';
|
|
273
273
|
const code = identityErrorCode(reason);
|
|
274
274
|
sendError(current.rpcId, code, reason);
|
|
275
|
-
//
|
|
275
|
+
// Bis: il canale e' terminato su timeout OLTRE che su revoke — dopo la
|
|
276
276
|
// risposta di errore fd4 va in EOF (la TUI vede il canale chiuso).
|
|
277
277
|
if (code === 'REVOKED' || reason === 'timeout') close(code, reason, current.rpcId, false);
|
|
278
278
|
};
|
|
@@ -397,7 +397,7 @@ function waitChild(child) {
|
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
// Delivery del bootstrap prompt per generazione (0.8.47,
|
|
400
|
+
// Delivery del bootstrap prompt per generazione (0.8.47, /).
|
|
401
401
|
//
|
|
402
402
|
// OWNER UNICO: per gli engine managed Kimi (client 'kimi'/'claude') il
|
|
403
403
|
// supervisore consegna il prompt per TUTTE le generazioni, gen0 compresa —
|
|
@@ -408,7 +408,7 @@ function waitChild(child) {
|
|
|
408
408
|
// massimo una nuova consegna. Engine custom send-keys: contratto legacy
|
|
409
409
|
// invariato (gen0 = runtime.injectPrompt; qui solo gen>0, paste senza Enter).
|
|
410
410
|
//
|
|
411
|
-
// CANCELLAZIONE
|
|
411
|
+
// CANCELLAZIONE: cancel ferma il timer E il polling in volo tramite
|
|
412
412
|
// isCancelled valutato dentro deliverBootstrapPrompt (ogni poll, pre-paste,
|
|
413
413
|
// pre-Enter); il chiamante fa cancel() + await settled prima dello spawn
|
|
414
414
|
// della generazione successiva: zero paste/Enter da un task della generazione
|
|
@@ -418,15 +418,15 @@ function waitChild(child) {
|
|
|
418
418
|
// (solo enum chiusi; l'opzione muore col pane, nessuno state file). Best
|
|
419
419
|
// effort: un set-option fallito lascia up() in report-timeout (onesto), la
|
|
420
420
|
// consegna resta fatta.
|
|
421
|
-
// CANCELLAZIONE (
|
|
421
|
+
// CANCELLAZIONE (+): cancel ferma il timer E il polling in volo tramite
|
|
422
422
|
// isCancelled valutato dentro deliverBootstrapPrompt (ogni poll, pre-paste,
|
|
423
423
|
// pre-Enter, post-Enter); il chiamante fa cancel() + await settled prima dello
|
|
424
424
|
// spawn della generazione successiva. cancel() con timer ANCORA PENDENTE
|
|
425
425
|
// (child uscito prima di readyMs, caso early-exit) risolve settled SUBITO:
|
|
426
|
-
//
|
|
426
|
+
// Mai deadlock. settled risolve con l'esito della delivery: null se
|
|
427
427
|
// nessun paste incerto, {state} se la generazione e' terminata con un
|
|
428
428
|
// post-paste incerto (delivery-unknown / staged-not-submitted) — il main loop
|
|
429
|
-
// NON auto-restarta in quel caso (
|
|
429
|
+
// NON auto-restarta in quel caso (leftover bytes potenzialmente residui nel PTY del
|
|
430
430
|
// pane riusato; fermo bounded + restart operatore).
|
|
431
431
|
function startGenerationPrompt(config, generation, childState, seams = {}) {
|
|
432
432
|
if (!config) return null;
|
|
@@ -474,7 +474,7 @@ function startGenerationPrompt(config, generation, childState, seams = {}) {
|
|
|
474
474
|
const state = result && typeof result.state === 'string' ? result.state : '';
|
|
475
475
|
const uncertain = state === 'delivery-unknown' || state === 'staged-not-submitted';
|
|
476
476
|
if (isCancelled()) {
|
|
477
|
-
//
|
|
477
|
+
// La generazione e' finita. Propago SOLO gli esiti post-paste
|
|
478
478
|
// incerti (residuo PTY possibile); cancelled pre-paste e' pulito.
|
|
479
479
|
return uncertain ? { state } : null;
|
|
480
480
|
}
|
|
@@ -482,7 +482,7 @@ function startGenerationPrompt(config, generation, childState, seams = {}) {
|
|
|
482
482
|
const kind = state === 'skipped-not-ready' && result.notReady ? `:${result.notReady}` : '';
|
|
483
483
|
await markDelivery(`${state}${kind}`);
|
|
484
484
|
}
|
|
485
|
-
//
|
|
485
|
+
// L'esito post-paste incerto va conservato ANCHE se il child
|
|
486
486
|
// era vivo al ritorno di deliver: i byte possono restare nel PTY del
|
|
487
487
|
// pane e sommarsi al bootstrap della generazione successiva. Al
|
|
488
488
|
// prossimo child exit il main ferma il supervisor (no auto-restart).
|
|
@@ -501,7 +501,7 @@ function startGenerationPrompt(config, generation, childState, seams = {}) {
|
|
|
501
501
|
timer.unref?.();
|
|
502
502
|
return {
|
|
503
503
|
settled,
|
|
504
|
-
//
|
|
504
|
+
// Timer ancora pendente -> settle IMMEDIATO (idempotente): il main
|
|
505
505
|
// loop non resta mai appeso su un child uscito prima di readyMs.
|
|
506
506
|
cancel() {
|
|
507
507
|
cancelled = true;
|
|
@@ -542,10 +542,10 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
542
542
|
if (process.env.TMUX) childEnv.TMUX = process.env.TMUX;
|
|
543
543
|
if (process.env.TMUX_PANE) childEnv.TMUX_PANE = process.env.TMUX_PANE;
|
|
544
544
|
|
|
545
|
-
//
|
|
546
|
-
// payload.env (
|
|
547
|
-
//
|
|
548
|
-
// inherit.
|
|
545
|
+
// No lease bearer ever reaches the child. childEnv derives ONLY from
|
|
546
|
+
// payload.env (plus TMUX); payload.lease (cellId/launchEpoch/stablePath) stays
|
|
547
|
+
// in the supervisor and feeds the lease-client. spawnImpl passes env + stdio
|
|
548
|
+
// inherit. The proof lives in the lease-client, NEVER in the child env.
|
|
549
549
|
let generation = 0;
|
|
550
550
|
let leaseCtl = null;
|
|
551
551
|
// da revisione interna: current/stopping vivono QUI (prima del lease-client) perche'
|
|
@@ -556,9 +556,9 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
556
556
|
leaseCtl = startLeaseClient(leaseSocket, {
|
|
557
557
|
stablePath: payload.lease.stablePath,
|
|
558
558
|
launchEpoch: payload.lease.launchEpoch,
|
|
559
|
-
//
|
|
560
|
-
// generation += 1).
|
|
561
|
-
//
|
|
559
|
+
// The generation ADVANCES with supervisor restarts (loop below,
|
|
560
|
+
// generation += 1). We pass a getter so the reconnect always presents
|
|
561
|
+
// the current generation, not a fixed 0 (reconciles :301 with :381).
|
|
562
562
|
generation: () => generation,
|
|
563
563
|
onIdentityDown: () => closeIdentityChannel('REVOKED', 'lease unavailable'),
|
|
564
564
|
// da revisione interna: lease persa per tutta la grace senza un
|
|
@@ -647,7 +647,7 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
647
647
|
resolve({ code: 1, signal: null, error });
|
|
648
648
|
});
|
|
649
649
|
});
|
|
650
|
-
//
|
|
650
|
+
// Transizione di generazione comunicata al lease PRIMA di aprire il
|
|
651
651
|
// canale identita' della generazione nuova. L'exit del figlio ha la
|
|
652
652
|
// precedenza sull'annuncio; FAIL-CLOSED (R1b): solo un annuncio
|
|
653
653
|
// RISOLTO con ok === true E generazione corrispondente apre il canale —
|
|
@@ -719,8 +719,8 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
719
719
|
childState.exited = true;
|
|
720
720
|
closeIdentityChannel();
|
|
721
721
|
current = null;
|
|
722
|
-
//
|
|
723
|
-
//
|
|
722
|
+
// : la generazione e' finita. Cancella la delivery in volo e ATTESA
|
|
723
|
+
// Del suo termine PRIMA di qualunque nuovo spawn.: se l'esito e' un
|
|
724
724
|
// post-paste incerto (delivery-unknown / staged-not-submitted) i byte del
|
|
725
725
|
// prompt possono essere residui nel PTY del pane riusato: NIENTE auto-
|
|
726
726
|
// restart — fermo bounded del supervisor, restart esplicito operatore.
|
package/lib/fleet/cell-hold.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// lib/fleet/cell-hold.js — placeholder inerte per l'avvio staged del pane Fleet.
|
|
4
|
-
// (
|
|
4
|
+
// (tmux-safe identity rules: no interactive shell)
|
|
5
5
|
//
|
|
6
6
|
// Resta in attesa finche' `respawn-pane -k` non lo sostituisce con il vero client
|
|
7
7
|
// (cell-exec). Crea il pane e la finestra in modo deterministico cosi' il runtime
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
// cio' impedisce a un child rapido di chiudere la sessione durante il setup. La
|
|
10
10
|
// normalizzazione dei punti nei nomi tmux e gestita separatamente dal mapping v2.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// (process.execPath + path
|
|
15
|
-
//
|
|
16
|
-
//
|
|
12
|
+
// Constraints: NO interactive shell, NO user rc/alias/plugin sourcing,
|
|
13
|
+
// NO sensitive data, NO interpreted arguments. Direct argv
|
|
14
|
+
// (process.execPath + absolute path of this file), purely blocking. It is not
|
|
15
|
+
// the real cell command: command/env/prompt/payload broker never appear
|
|
16
|
+
// in the tmux argv.
|
|
17
17
|
//
|
|
18
18
|
// Uscita silenziosa sul segnale di terminazione inviato da respawn-pane -k.
|
|
19
19
|
|