@vascend/talos 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/backend/asset-events.js +9 -9
- package/dist/core/agent.js +3 -3
- package/dist/core/cli-args.js +50 -50
- package/dist/core/cli-installation.d.ts +4 -0
- package/dist/core/cli-installation.js +55 -0
- package/dist/core/compaction.js +26 -26
- package/dist/core/config.js +101 -101
- package/dist/core/execution-guidance.js +21 -21
- package/dist/core/package-update.d.ts +3 -1
- package/dist/core/package-update.js +19 -6
- package/dist/core/self-update.d.ts +1 -1
- package/dist/core/signed-release.d.ts +33 -0
- package/dist/core/signed-release.js +121 -0
- package/dist/core/update-check.d.ts +1 -1
- package/dist/core/update-check.js +15 -14
- package/dist/core/vibe-modes.js +13 -13
- package/dist/distribution/install.d.ts +1 -0
- package/dist/distribution/install.js +31 -0
- package/dist/install.mjs +271 -0
- package/dist/node-runtime.json +13 -0
- package/dist/providers/oauth-callback-server.js +32 -32
- package/dist/tools/capabilities.js +30 -30
- package/dist/tools/computer-capture.js +50 -50
- package/dist/tools/program.js +82 -82
- package/dist/tui/i18n-catalog.js +16 -0
- package/dist/tui/quick-help.js +31 -31
- package/dist/tui/repl.js +5 -5
- package/dist/tui/update-prompt.js +1 -0
- package/dist/vascend/code-events.js +9 -9
- package/dist/vascend/completeness.js +31 -31
- package/dist/vascend/context-fabric.js +16 -16
- package/dist/vascend/contract-memory.js +30 -30
- package/dist/vascend/council.js +82 -82
- package/dist/vascend/decision-promotion.js +23 -23
- package/dist/vascend/granular-kingdom.js +17 -17
- package/dist/vascend/granular-launcher.js +2 -2
- package/dist/vascend/kingdom-kpi.js +19 -19
- package/dist/vascend/kingdom-run.js +6 -6
- package/dist/vascend/kingdom.js +56 -56
- package/dist/vascend/mandate.js +21 -21
- package/dist/vascend/objective-revision.js +34 -34
- package/dist/vascend/patch-plan.js +22 -22
- package/dist/vascend/requirements.js +29 -29
- package/dist/vascend/reviewer.js +26 -26
- package/dist/vascend/room-memory.js +25 -25
- package/dist/vascend/runtime.js +44 -44
- package/dist/vascend/self-improve.js +39 -39
- package/dist/vascend/test-failure-memory.js +9 -9
- package/dist/vascend/vision.js +18 -18
- package/dist/vendor/computer-use/README.md +7 -7
- package/dist/vendor/computer-use/computer-desktop.js +211 -211
- package/dist/vendor/computer-use/computer-observe.js +315 -315
- package/dist/vendor/computer-use/computer-scripts.js +1230 -1230
- package/dist/vendor/computer-use/computer-tools.js +2136 -2136
- package/dist/vendor/computer-use/package.json +1 -1
- package/dist/vendor/computer-use/rac/atomic-input.js +91 -91
- package/dist/vendor/computer-use/rac/grammatica.js +101 -101
- package/dist/vendor/computer-use/rac/parser.js +185 -185
- package/dist/vendor/computer-use/rac/protocol.js +136 -136
- package/install.ps1 +374 -30
- package/install.sh +356 -43
- package/package.json +2 -2
- package/dist/core/kingdoms-mode-state.d.ts +0 -24
- package/dist/core/kingdoms-mode-state.js +0 -139
- package/dist/text-sanitize.js +0 -28
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"commonjs","private":true}
|
|
1
|
+
{"type":"commonjs","private":true}
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const MAX_OPERATIONS = 12;
|
|
4
|
-
const MAX_TEXT = 4000;
|
|
5
|
-
|
|
6
|
-
const KEY_ALIASES = Object.freeze({
|
|
7
|
-
ctrl: 'Ctrl', control: 'Ctrl', shift: 'Shift', alt: 'Alt',
|
|
8
|
-
space: 'Space', tab: 'Tab', esc: 'Esc', escape: 'Esc',
|
|
9
|
-
enter: 'Enter', return: 'Enter', backspace: 'Backspace', delete: 'Delete', del: 'Delete',
|
|
10
|
-
home: 'Home', end: 'End', left: 'ArrowLeft', arrowleft: 'ArrowLeft',
|
|
11
|
-
up: 'ArrowUp', arrowup: 'ArrowUp', right: 'ArrowRight', arrowright: 'ArrowRight',
|
|
12
|
-
down: 'ArrowDown', arrowdown: 'ArrowDown',
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
function normalizeKey(value) {
|
|
16
|
-
const raw = String(value || '').trim();
|
|
17
|
-
if (!raw) return null;
|
|
18
|
-
const alias = KEY_ALIASES[raw.toLowerCase()];
|
|
19
|
-
if (alias) return alias;
|
|
20
|
-
if (/^[a-z0-9]$/i.test(raw)) return raw.toUpperCase();
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function normalizeShortcut(keys) {
|
|
25
|
-
const source = Array.isArray(keys)
|
|
26
|
-
? keys
|
|
27
|
-
: String(keys || '').split(/[+\s]+/).filter(Boolean);
|
|
28
|
-
if (!source.length || source.length > 4) {
|
|
29
|
-
return { ok: false, code: 'BAD_SHORTCUT', error: 'Una scorciatoia richiede da uno a quattro tasti.' };
|
|
30
|
-
}
|
|
31
|
-
const normalized = [];
|
|
32
|
-
for (const value of source) {
|
|
33
|
-
const key = normalizeKey(value);
|
|
34
|
-
if (!key) return { ok: false, code: 'BAD_SHORTCUT', error: `Tasto non ammesso: ${String(value)}` };
|
|
35
|
-
if (!normalized.includes(key)) normalized.push(key);
|
|
36
|
-
}
|
|
37
|
-
return { ok: true, keys: normalized };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function normalizeAtomicOperations(value) {
|
|
41
|
-
if (!Array.isArray(value) || !value.length || value.length > MAX_OPERATIONS) {
|
|
42
|
-
return { ok: false, code: 'BAD_OPERATIONS', error: `Servono da 1 a ${MAX_OPERATIONS} operazioni.` };
|
|
43
|
-
}
|
|
44
|
-
const operations = [];
|
|
45
|
-
for (const raw of value) {
|
|
46
|
-
const type = String(raw && raw.type || '').trim().toLowerCase();
|
|
47
|
-
if (type === 'shortcut') {
|
|
48
|
-
const shortcut = normalizeShortcut(raw.keys);
|
|
49
|
-
if (!shortcut.ok) return shortcut;
|
|
50
|
-
operations.push({ type, keys: shortcut.keys });
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (type === 'text') {
|
|
54
|
-
const text = String(raw && raw.text != null ? raw.text : '');
|
|
55
|
-
if (!text || text.length > MAX_TEXT || /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(text)) {
|
|
56
|
-
return { ok: false, code: 'BAD_TEXT', error: `Il testo deve contenere da 1 a ${MAX_TEXT} caratteri stampabili.` };
|
|
57
|
-
}
|
|
58
|
-
operations.push({ type, text });
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (type === 'mouse') {
|
|
62
|
-
const dx = Number(raw && raw.dx || 0);
|
|
63
|
-
const dy = Number(raw && raw.dy || 0);
|
|
64
|
-
const scroll = Number(raw && raw.scroll || 0);
|
|
65
|
-
if (![dx, dy, scroll].every(Number.isInteger)
|
|
66
|
-
|| Math.abs(dx) > 1000 || Math.abs(dy) > 1000 || Math.abs(scroll) > 5) {
|
|
67
|
-
return { ok: false, code: 'BAD_MOUSE', error: 'Mouse fuori range: dx/dy ±1000, scroll ±5.' };
|
|
68
|
-
}
|
|
69
|
-
operations.push({ type, dx, dy, scroll });
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (type === 'click') {
|
|
73
|
-
const button = String(raw && raw.button || 'left').toLowerCase();
|
|
74
|
-
if (!['left', 'right', 'middle'].includes(button)) {
|
|
75
|
-
return { ok: false, code: 'BAD_BUTTON', error: 'Pulsante ammesso: left, right o middle.' };
|
|
76
|
-
}
|
|
77
|
-
operations.push({ type, button });
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
return { ok: false, code: 'BAD_OPERATION', error: `Operazione non ammessa: ${type || '(vuota)'}` };
|
|
81
|
-
}
|
|
82
|
-
return { ok: true, operations };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
module.exports = {
|
|
86
|
-
MAX_OPERATIONS,
|
|
87
|
-
MAX_TEXT,
|
|
88
|
-
normalizeKey,
|
|
89
|
-
normalizeShortcut,
|
|
90
|
-
normalizeAtomicOperations,
|
|
91
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MAX_OPERATIONS = 12;
|
|
4
|
+
const MAX_TEXT = 4000;
|
|
5
|
+
|
|
6
|
+
const KEY_ALIASES = Object.freeze({
|
|
7
|
+
ctrl: 'Ctrl', control: 'Ctrl', shift: 'Shift', alt: 'Alt',
|
|
8
|
+
space: 'Space', tab: 'Tab', esc: 'Esc', escape: 'Esc',
|
|
9
|
+
enter: 'Enter', return: 'Enter', backspace: 'Backspace', delete: 'Delete', del: 'Delete',
|
|
10
|
+
home: 'Home', end: 'End', left: 'ArrowLeft', arrowleft: 'ArrowLeft',
|
|
11
|
+
up: 'ArrowUp', arrowup: 'ArrowUp', right: 'ArrowRight', arrowright: 'ArrowRight',
|
|
12
|
+
down: 'ArrowDown', arrowdown: 'ArrowDown',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function normalizeKey(value) {
|
|
16
|
+
const raw = String(value || '').trim();
|
|
17
|
+
if (!raw) return null;
|
|
18
|
+
const alias = KEY_ALIASES[raw.toLowerCase()];
|
|
19
|
+
if (alias) return alias;
|
|
20
|
+
if (/^[a-z0-9]$/i.test(raw)) return raw.toUpperCase();
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeShortcut(keys) {
|
|
25
|
+
const source = Array.isArray(keys)
|
|
26
|
+
? keys
|
|
27
|
+
: String(keys || '').split(/[+\s]+/).filter(Boolean);
|
|
28
|
+
if (!source.length || source.length > 4) {
|
|
29
|
+
return { ok: false, code: 'BAD_SHORTCUT', error: 'Una scorciatoia richiede da uno a quattro tasti.' };
|
|
30
|
+
}
|
|
31
|
+
const normalized = [];
|
|
32
|
+
for (const value of source) {
|
|
33
|
+
const key = normalizeKey(value);
|
|
34
|
+
if (!key) return { ok: false, code: 'BAD_SHORTCUT', error: `Tasto non ammesso: ${String(value)}` };
|
|
35
|
+
if (!normalized.includes(key)) normalized.push(key);
|
|
36
|
+
}
|
|
37
|
+
return { ok: true, keys: normalized };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeAtomicOperations(value) {
|
|
41
|
+
if (!Array.isArray(value) || !value.length || value.length > MAX_OPERATIONS) {
|
|
42
|
+
return { ok: false, code: 'BAD_OPERATIONS', error: `Servono da 1 a ${MAX_OPERATIONS} operazioni.` };
|
|
43
|
+
}
|
|
44
|
+
const operations = [];
|
|
45
|
+
for (const raw of value) {
|
|
46
|
+
const type = String(raw && raw.type || '').trim().toLowerCase();
|
|
47
|
+
if (type === 'shortcut') {
|
|
48
|
+
const shortcut = normalizeShortcut(raw.keys);
|
|
49
|
+
if (!shortcut.ok) return shortcut;
|
|
50
|
+
operations.push({ type, keys: shortcut.keys });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (type === 'text') {
|
|
54
|
+
const text = String(raw && raw.text != null ? raw.text : '');
|
|
55
|
+
if (!text || text.length > MAX_TEXT || /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(text)) {
|
|
56
|
+
return { ok: false, code: 'BAD_TEXT', error: `Il testo deve contenere da 1 a ${MAX_TEXT} caratteri stampabili.` };
|
|
57
|
+
}
|
|
58
|
+
operations.push({ type, text });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (type === 'mouse') {
|
|
62
|
+
const dx = Number(raw && raw.dx || 0);
|
|
63
|
+
const dy = Number(raw && raw.dy || 0);
|
|
64
|
+
const scroll = Number(raw && raw.scroll || 0);
|
|
65
|
+
if (![dx, dy, scroll].every(Number.isInteger)
|
|
66
|
+
|| Math.abs(dx) > 1000 || Math.abs(dy) > 1000 || Math.abs(scroll) > 5) {
|
|
67
|
+
return { ok: false, code: 'BAD_MOUSE', error: 'Mouse fuori range: dx/dy ±1000, scroll ±5.' };
|
|
68
|
+
}
|
|
69
|
+
operations.push({ type, dx, dy, scroll });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (type === 'click') {
|
|
73
|
+
const button = String(raw && raw.button || 'left').toLowerCase();
|
|
74
|
+
if (!['left', 'right', 'middle'].includes(button)) {
|
|
75
|
+
return { ok: false, code: 'BAD_BUTTON', error: 'Pulsante ammesso: left, right o middle.' };
|
|
76
|
+
}
|
|
77
|
+
operations.push({ type, button });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
return { ok: false, code: 'BAD_OPERATION', error: `Operazione non ammessa: ${type || '(vuota)'}` };
|
|
81
|
+
}
|
|
82
|
+
return { ok: true, operations };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
MAX_OPERATIONS,
|
|
87
|
+
MAX_TEXT,
|
|
88
|
+
normalizeKey,
|
|
89
|
+
normalizeShortcut,
|
|
90
|
+
normalizeAtomicOperations,
|
|
91
|
+
};
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// MCP-RAC — grammatica computer-use v0.1 (spec §6.4).
|
|
4
|
-
//
|
|
5
|
-
// Un'azione e' un blocco:
|
|
6
|
-
//
|
|
7
|
-
// ACTION <observation_id> ttl=<ms>
|
|
8
|
-
// <dx> <dy> <scroll> ; K1 ; K2 ; ... ; K6 END
|
|
9
|
-
//
|
|
10
|
-
// Il primo segmento e' il MOVIMENTO (relativo, distribuito sull'orizzonte);
|
|
11
|
-
// i successivi sono KEYSET: l'insieme dei tasti tenuti premuti per 33 ms.
|
|
12
|
-
// `_` e' il keyset vuoto (tutto rilasciato). Le chiavi assenti si rilasciano.
|
|
13
|
-
//
|
|
14
|
-
// La grammatica e' volutamente povera: niente Enter, niente Canc, niente tasto
|
|
15
|
-
// Windows. Non e' una dimenticanza — e' il primo strato di sicurezza (spec §13):
|
|
16
|
-
// le azioni irreversibili non devono essere ESPRIMIBILI nel canale reattivo,
|
|
17
|
-
// prima ancora che vietate da una policy.
|
|
18
|
-
|
|
19
|
-
const LIMITI = Object.freeze({
|
|
20
|
-
MOUSE_DELTA: 1000, // dx, dy in [-1000, 1000]
|
|
21
|
-
SCROLL: 5, // scroll in [-5, 5]
|
|
22
|
-
TASTI_PER_CHUNK: 4, // spec §13: "max quattro tasti/chunk"
|
|
23
|
-
CHUNK_PER_AZIONE: 6, // 6 keyset da 33 ms = orizzonte 200 ms
|
|
24
|
-
TTL_MASSIMO_MS: 250, // oltre la lease il chunk non ha senso
|
|
25
|
-
CHUNK_MS: 33, // durata nominale di un keyset
|
|
26
|
-
ORIZZONTE_MS: 200,
|
|
27
|
-
BUFFER_MASSIMO: 16 * 1024,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// L'alfabeto completo dei tasti ammessi. Un token fuori da qui non e' "un tasto
|
|
31
|
-
// sconosciuto da inoltrare": e' un errore di grammatica che avvelena l'azione.
|
|
32
|
-
const TASTI_INTERNI = (() => {
|
|
33
|
-
const out = new Set(['Shift', 'Ctrl', 'Alt', 'Space', 'Tab', 'Esc', 'LB', 'RB', 'MB']);
|
|
34
|
-
for (let c = 65; c <= 90; c += 1) out.add(String.fromCharCode(c)); // A..Z
|
|
35
|
-
for (let d = 0; d <= 9; d += 1) out.add(String(d)); // 0..9
|
|
36
|
-
return out;
|
|
37
|
-
})();
|
|
38
|
-
// Un Set congelato resta modificabile tramite add/delete. Esponiamo quindi una
|
|
39
|
-
// fotografia realmente immutabile e teniamo il Set usato dalla validazione
|
|
40
|
-
// chiuso nel modulo.
|
|
41
|
-
const TASTI = Object.freeze([...TASTI_INTERNI].sort());
|
|
42
|
-
|
|
43
|
-
function interoIn(testo, min, max) {
|
|
44
|
-
if (!/^-?\d+$/.test(testo)) return null;
|
|
45
|
-
const n = Number(testo);
|
|
46
|
-
return n >= min && n <= max ? n : null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// "ACTION 184 ttl=250" -> { observationId, ttlMs } | { errore }
|
|
50
|
-
function analizzaIntestazione(riga) {
|
|
51
|
-
const m = /^ACTION\s+(\d+)\s+ttl=(\d+)$/.exec(String(riga || '').trim());
|
|
52
|
-
if (!m) return { errore: 'intestazione malformata' };
|
|
53
|
-
const observationNumber = Number(m[1]);
|
|
54
|
-
const ttlMs = Number(m[2]);
|
|
55
|
-
if (!Number.isSafeInteger(observationNumber)) return { errore: 'observation_id fuori limite' };
|
|
56
|
-
if (ttlMs <= 0 || ttlMs > LIMITI.TTL_MASSIMO_MS) {
|
|
57
|
-
return { errore: `ttl fuori limite (1..${LIMITI.TTL_MASSIMO_MS})` };
|
|
58
|
-
}
|
|
59
|
-
return { observationId: m[1], ttlMs };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// "92 0 0" -> { tipo: 'mouse', dx, dy, scroll } | { errore }
|
|
63
|
-
function analizzaMouse(segmento) {
|
|
64
|
-
const parti = String(segmento || '').trim().split(/\s+/);
|
|
65
|
-
if (parti.length !== 3) return { errore: 'il movimento vuole esattamente dx dy scroll' };
|
|
66
|
-
const dx = interoIn(parti[0], -LIMITI.MOUSE_DELTA, LIMITI.MOUSE_DELTA);
|
|
67
|
-
const dy = interoIn(parti[1], -LIMITI.MOUSE_DELTA, LIMITI.MOUSE_DELTA);
|
|
68
|
-
const scroll = interoIn(parti[2], -LIMITI.SCROLL, LIMITI.SCROLL);
|
|
69
|
-
if (dx === null || dy === null || scroll === null) return { errore: 'movimento fuori range' };
|
|
70
|
-
return { tipo: 'mouse', dx, dy, scroll };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// "Shift W" | "_" -> { tipo: 'keyset', tasti: [...] } | { errore }
|
|
74
|
-
// I tasti si normalizzano ordinati e senza duplicati: {W,Shift} e {Shift,W}
|
|
75
|
-
// sono lo stesso stato fisico, e l'esecutore confronta insiemi.
|
|
76
|
-
function analizzaKeyset(segmento) {
|
|
77
|
-
const testo = String(segmento || '').trim();
|
|
78
|
-
if (testo === '_') return { tipo: 'keyset', tasti: [] };
|
|
79
|
-
if (!testo) return { errore: 'keyset vuoto: usa _ per rilasciare tutti i tasti' };
|
|
80
|
-
const token = testo.split(/\s+/);
|
|
81
|
-
if (token.length > LIMITI.TASTI_PER_CHUNK) {
|
|
82
|
-
return { errore: `piu' di ${LIMITI.TASTI_PER_CHUNK} tasti in un chunk` };
|
|
83
|
-
}
|
|
84
|
-
const visti = new Set();
|
|
85
|
-
for (const t of token) {
|
|
86
|
-
if (!TASTI_INTERNI.has(t)) return { errore: `tasto fuori alfabeto: ${t}` };
|
|
87
|
-
visti.add(t);
|
|
88
|
-
}
|
|
89
|
-
return { tipo: 'keyset', tasti: [...visti].sort() };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Serializzazione, per il simulatore e i replay: da azione strutturata al testo
|
|
93
|
-
// che il decoder emetterebbe. Round-trip garantito dai test.
|
|
94
|
-
function serializza(azione) {
|
|
95
|
-
const testa = `ACTION ${azione.observationId} ttl=${azione.ttlMs}\n`;
|
|
96
|
-
const mouse = `${azione.mouse.dx} ${azione.mouse.dy} ${azione.mouse.scroll}`;
|
|
97
|
-
const keyset = azione.keyset.map((k) => (k.length ? k.join(' ') : '_'));
|
|
98
|
-
return testa + [mouse, ...keyset].join(' ; ') + ' ;\nEND';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
module.exports = { LIMITI, TASTI, analizzaIntestazione, analizzaMouse, analizzaKeyset, serializza };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// MCP-RAC — grammatica computer-use v0.1 (spec §6.4).
|
|
4
|
+
//
|
|
5
|
+
// Un'azione e' un blocco:
|
|
6
|
+
//
|
|
7
|
+
// ACTION <observation_id> ttl=<ms>
|
|
8
|
+
// <dx> <dy> <scroll> ; K1 ; K2 ; ... ; K6 END
|
|
9
|
+
//
|
|
10
|
+
// Il primo segmento e' il MOVIMENTO (relativo, distribuito sull'orizzonte);
|
|
11
|
+
// i successivi sono KEYSET: l'insieme dei tasti tenuti premuti per 33 ms.
|
|
12
|
+
// `_` e' il keyset vuoto (tutto rilasciato). Le chiavi assenti si rilasciano.
|
|
13
|
+
//
|
|
14
|
+
// La grammatica e' volutamente povera: niente Enter, niente Canc, niente tasto
|
|
15
|
+
// Windows. Non e' una dimenticanza — e' il primo strato di sicurezza (spec §13):
|
|
16
|
+
// le azioni irreversibili non devono essere ESPRIMIBILI nel canale reattivo,
|
|
17
|
+
// prima ancora che vietate da una policy.
|
|
18
|
+
|
|
19
|
+
const LIMITI = Object.freeze({
|
|
20
|
+
MOUSE_DELTA: 1000, // dx, dy in [-1000, 1000]
|
|
21
|
+
SCROLL: 5, // scroll in [-5, 5]
|
|
22
|
+
TASTI_PER_CHUNK: 4, // spec §13: "max quattro tasti/chunk"
|
|
23
|
+
CHUNK_PER_AZIONE: 6, // 6 keyset da 33 ms = orizzonte 200 ms
|
|
24
|
+
TTL_MASSIMO_MS: 250, // oltre la lease il chunk non ha senso
|
|
25
|
+
CHUNK_MS: 33, // durata nominale di un keyset
|
|
26
|
+
ORIZZONTE_MS: 200,
|
|
27
|
+
BUFFER_MASSIMO: 16 * 1024,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// L'alfabeto completo dei tasti ammessi. Un token fuori da qui non e' "un tasto
|
|
31
|
+
// sconosciuto da inoltrare": e' un errore di grammatica che avvelena l'azione.
|
|
32
|
+
const TASTI_INTERNI = (() => {
|
|
33
|
+
const out = new Set(['Shift', 'Ctrl', 'Alt', 'Space', 'Tab', 'Esc', 'LB', 'RB', 'MB']);
|
|
34
|
+
for (let c = 65; c <= 90; c += 1) out.add(String.fromCharCode(c)); // A..Z
|
|
35
|
+
for (let d = 0; d <= 9; d += 1) out.add(String(d)); // 0..9
|
|
36
|
+
return out;
|
|
37
|
+
})();
|
|
38
|
+
// Un Set congelato resta modificabile tramite add/delete. Esponiamo quindi una
|
|
39
|
+
// fotografia realmente immutabile e teniamo il Set usato dalla validazione
|
|
40
|
+
// chiuso nel modulo.
|
|
41
|
+
const TASTI = Object.freeze([...TASTI_INTERNI].sort());
|
|
42
|
+
|
|
43
|
+
function interoIn(testo, min, max) {
|
|
44
|
+
if (!/^-?\d+$/.test(testo)) return null;
|
|
45
|
+
const n = Number(testo);
|
|
46
|
+
return n >= min && n <= max ? n : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// "ACTION 184 ttl=250" -> { observationId, ttlMs } | { errore }
|
|
50
|
+
function analizzaIntestazione(riga) {
|
|
51
|
+
const m = /^ACTION\s+(\d+)\s+ttl=(\d+)$/.exec(String(riga || '').trim());
|
|
52
|
+
if (!m) return { errore: 'intestazione malformata' };
|
|
53
|
+
const observationNumber = Number(m[1]);
|
|
54
|
+
const ttlMs = Number(m[2]);
|
|
55
|
+
if (!Number.isSafeInteger(observationNumber)) return { errore: 'observation_id fuori limite' };
|
|
56
|
+
if (ttlMs <= 0 || ttlMs > LIMITI.TTL_MASSIMO_MS) {
|
|
57
|
+
return { errore: `ttl fuori limite (1..${LIMITI.TTL_MASSIMO_MS})` };
|
|
58
|
+
}
|
|
59
|
+
return { observationId: m[1], ttlMs };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// "92 0 0" -> { tipo: 'mouse', dx, dy, scroll } | { errore }
|
|
63
|
+
function analizzaMouse(segmento) {
|
|
64
|
+
const parti = String(segmento || '').trim().split(/\s+/);
|
|
65
|
+
if (parti.length !== 3) return { errore: 'il movimento vuole esattamente dx dy scroll' };
|
|
66
|
+
const dx = interoIn(parti[0], -LIMITI.MOUSE_DELTA, LIMITI.MOUSE_DELTA);
|
|
67
|
+
const dy = interoIn(parti[1], -LIMITI.MOUSE_DELTA, LIMITI.MOUSE_DELTA);
|
|
68
|
+
const scroll = interoIn(parti[2], -LIMITI.SCROLL, LIMITI.SCROLL);
|
|
69
|
+
if (dx === null || dy === null || scroll === null) return { errore: 'movimento fuori range' };
|
|
70
|
+
return { tipo: 'mouse', dx, dy, scroll };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// "Shift W" | "_" -> { tipo: 'keyset', tasti: [...] } | { errore }
|
|
74
|
+
// I tasti si normalizzano ordinati e senza duplicati: {W,Shift} e {Shift,W}
|
|
75
|
+
// sono lo stesso stato fisico, e l'esecutore confronta insiemi.
|
|
76
|
+
function analizzaKeyset(segmento) {
|
|
77
|
+
const testo = String(segmento || '').trim();
|
|
78
|
+
if (testo === '_') return { tipo: 'keyset', tasti: [] };
|
|
79
|
+
if (!testo) return { errore: 'keyset vuoto: usa _ per rilasciare tutti i tasti' };
|
|
80
|
+
const token = testo.split(/\s+/);
|
|
81
|
+
if (token.length > LIMITI.TASTI_PER_CHUNK) {
|
|
82
|
+
return { errore: `piu' di ${LIMITI.TASTI_PER_CHUNK} tasti in un chunk` };
|
|
83
|
+
}
|
|
84
|
+
const visti = new Set();
|
|
85
|
+
for (const t of token) {
|
|
86
|
+
if (!TASTI_INTERNI.has(t)) return { errore: `tasto fuori alfabeto: ${t}` };
|
|
87
|
+
visti.add(t);
|
|
88
|
+
}
|
|
89
|
+
return { tipo: 'keyset', tasti: [...visti].sort() };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Serializzazione, per il simulatore e i replay: da azione strutturata al testo
|
|
93
|
+
// che il decoder emetterebbe. Round-trip garantito dai test.
|
|
94
|
+
function serializza(azione) {
|
|
95
|
+
const testa = `ACTION ${azione.observationId} ttl=${azione.ttlMs}\n`;
|
|
96
|
+
const mouse = `${azione.mouse.dx} ${azione.mouse.dy} ${azione.mouse.scroll}`;
|
|
97
|
+
const keyset = azione.keyset.map((k) => (k.length ? k.join(' ') : '_'));
|
|
98
|
+
return testa + [mouse, ...keyset].join(' ; ') + ' ;\nEND';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { LIMITI, TASTI, analizzaIntestazione, analizzaMouse, analizzaKeyset, serializza };
|