@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
|
@@ -219,27 +219,27 @@ export function visionToText(vision) {
|
|
|
219
219
|
// System prompt del motore (notazione Danilov): confronta STATO vs VISION e
|
|
220
220
|
// produce proposte di miglioramento. SOLO JSON in output.
|
|
221
221
|
// ---------------------------------------------------------------------------
|
|
222
|
-
const ENGINE_SYSTEM = `# Motore di auto-miglioramento — confronto STATO vs VISION
|
|
223
|
-
INDICE
|
|
224
|
-
1 = vision
|
|
225
|
-
2 = stato
|
|
226
|
-
3 = gap
|
|
227
|
-
4 = proposta
|
|
228
|
-
|
|
229
|
-
DEFINIZIONI
|
|
230
|
-
@vision[finale]: lo stato IDEALE desiderato dall'utente (obiettivo finale, valore, funzionalita' attese, criteri di pronto)
|
|
231
|
-
@stato[corrente]: cio' che e' stato effettivamente realizzato finora (operato, attivita, output)
|
|
232
|
-
@gap[misura]: distanza stato->vision; alignment 0..100 (100=vision gia' realizzata, 0=lontanissimo)
|
|
233
|
-
@proposta[regola]: ogni proposta AVVICINA lo stato alla Vision; concreta, azionabile, frase imperativa breve
|
|
234
|
-
@proposta[campi]: category(feature|quality|ux|robustness|performance|alignment), priority(must|should|could), impact 0..100, effort(small|medium|large), visionRef(feature/criterio di riferimento se noto), rationale
|
|
235
|
-
@priorita[criterio]: must=essenziale per la Vision, should=migliora molto, could=rifinitura
|
|
236
|
-
@bilancia[no_rumore]: proponi SOLO miglioramenti reali verso la Vision; niente perfezionismo, niente proposte generiche
|
|
237
|
-
|
|
238
|
-
RELAZIONI
|
|
239
|
-
@R1: @vision[finale] -> @gap[misura] [ misura quanto manca alla Vision ]
|
|
240
|
-
@R2: @gap[misura] -> @proposta[regola] [ ogni gap diventa una proposta azionabile ]
|
|
241
|
-
@R3: @proposta[regola] -> @priorita[criterio] [ ordina per impatto sulla Vision ]
|
|
242
|
-
|
|
222
|
+
const ENGINE_SYSTEM = `# Motore di auto-miglioramento — confronto STATO vs VISION
|
|
223
|
+
INDICE
|
|
224
|
+
1 = vision
|
|
225
|
+
2 = stato
|
|
226
|
+
3 = gap
|
|
227
|
+
4 = proposta
|
|
228
|
+
|
|
229
|
+
DEFINIZIONI
|
|
230
|
+
@vision[finale]: lo stato IDEALE desiderato dall'utente (obiettivo finale, valore, funzionalita' attese, criteri di pronto)
|
|
231
|
+
@stato[corrente]: cio' che e' stato effettivamente realizzato finora (operato, attivita, output)
|
|
232
|
+
@gap[misura]: distanza stato->vision; alignment 0..100 (100=vision gia' realizzata, 0=lontanissimo)
|
|
233
|
+
@proposta[regola]: ogni proposta AVVICINA lo stato alla Vision; concreta, azionabile, frase imperativa breve
|
|
234
|
+
@proposta[campi]: category(feature|quality|ux|robustness|performance|alignment), priority(must|should|could), impact 0..100, effort(small|medium|large), visionRef(feature/criterio di riferimento se noto), rationale
|
|
235
|
+
@priorita[criterio]: must=essenziale per la Vision, should=migliora molto, could=rifinitura
|
|
236
|
+
@bilancia[no_rumore]: proponi SOLO miglioramenti reali verso la Vision; niente perfezionismo, niente proposte generiche
|
|
237
|
+
|
|
238
|
+
RELAZIONI
|
|
239
|
+
@R1: @vision[finale] -> @gap[misura] [ misura quanto manca alla Vision ]
|
|
240
|
+
@R2: @gap[misura] -> @proposta[regola] [ ogni gap diventa una proposta azionabile ]
|
|
241
|
+
@R3: @proposta[regola] -> @priorita[criterio] [ ordina per impatto sulla Vision ]
|
|
242
|
+
|
|
243
243
|
OUTPUT: SOLO JSON {"alignment": 0-100, "proposals": [{"text","category","priority","impact","effort","visionRef","rationale"}]}. Niente prosa fuori dal JSON, niente markdown.`;
|
|
244
244
|
// Normalizza un singolo oggetto grezzo in ImprovementProposal, o null se vuoto.
|
|
245
245
|
export function normalizeProposal(raw) {
|
|
@@ -352,24 +352,24 @@ export function buildSelfImproveSteer(goal, report) {
|
|
|
352
352
|
.map((p, i) => `(${i + 1}) [${p.priority}/${p.category}] ${p.text}${p.visionRef ? ` <${p.visionRef}>` : ""} (impatto ${p.impact})`)
|
|
353
353
|
.join(" ; ");
|
|
354
354
|
const mustCount = report.proposals.filter((p) => p.priority === "must").length;
|
|
355
|
-
return `[VASCEND_AUTO_MIGLIORAMENTO]
|
|
356
|
-
INDICE
|
|
357
|
-
1 = goal
|
|
358
|
-
2 = vision
|
|
359
|
-
3 = proposta
|
|
360
|
-
4 = azione
|
|
361
|
-
|
|
362
|
-
DEFINIZIONI
|
|
363
|
-
@goal[reale]: "${g}"
|
|
364
|
-
@vision[allineamento]: stato_corrente=${report.alignment}/100 verso la Vision finale (must residui=${mustCount})
|
|
365
|
-
@proposta[miglioramenti]: ${rows || "nessuna proposta esplicita: rifinisci verso la Vision"}
|
|
366
|
-
@azione[migliora]: NON ti fermi allo stato attuale; avvicina il lavoro alla VISION finale realizzando le proposte (must prima)
|
|
367
|
-
|
|
368
|
-
RELAZIONI
|
|
369
|
-
@R1: @vision[allineamento] -> @proposta[miglioramenti] [ il gap verso la Vision diventa proposte ]
|
|
370
|
-
@R2: @proposta[miglioramenti] -> @azione[migliora] [ realizza prima le proposte must, poi should ]
|
|
371
|
-
@R3: @azione[migliora] -> @goal[reale] [ finche' lo stato non e' allineato alla Vision ]
|
|
372
|
-
|
|
355
|
+
return `[VASCEND_AUTO_MIGLIORAMENTO]
|
|
356
|
+
INDICE
|
|
357
|
+
1 = goal
|
|
358
|
+
2 = vision
|
|
359
|
+
3 = proposta
|
|
360
|
+
4 = azione
|
|
361
|
+
|
|
362
|
+
DEFINIZIONI
|
|
363
|
+
@goal[reale]: "${g}"
|
|
364
|
+
@vision[allineamento]: stato_corrente=${report.alignment}/100 verso la Vision finale (must residui=${mustCount})
|
|
365
|
+
@proposta[miglioramenti]: ${rows || "nessuna proposta esplicita: rifinisci verso la Vision"}
|
|
366
|
+
@azione[migliora]: NON ti fermi allo stato attuale; avvicina il lavoro alla VISION finale realizzando le proposte (must prima)
|
|
367
|
+
|
|
368
|
+
RELAZIONI
|
|
369
|
+
@R1: @vision[allineamento] -> @proposta[miglioramenti] [ il gap verso la Vision diventa proposte ]
|
|
370
|
+
@R2: @proposta[miglioramenti] -> @azione[migliora] [ realizza prima le proposte must, poi should ]
|
|
371
|
+
@R3: @azione[migliora] -> @goal[reale] [ finche' lo stato non e' allineato alla Vision ]
|
|
372
|
+
|
|
373
373
|
OUTPUT: realizza le proposte di miglioramento elencate (priorita' must prima) col lavoro reale e completa le attivita con vascend_mark. Punta ad allineare lo stato alla Vision: il motore rivaluta.`;
|
|
374
374
|
}
|
|
375
375
|
// Sintesi compatta del report (per log/TUI), in una riga.
|
|
@@ -103,15 +103,15 @@ export function formatTestFailureRecord(room, event) {
|
|
|
103
103
|
: event.type === "test.fixed"
|
|
104
104
|
? `risolto da: ${escapeValue(event.fixedBy ?? "(non indicato)")}`
|
|
105
105
|
: `superato da un fallimento piu' recente o non piu' rilevante`;
|
|
106
|
-
const body = `INDICE
|
|
107
|
-
${uniqTerms.map((t, i) => `${i + 1} = ${t}`).join("\n")}
|
|
108
|
-
|
|
109
|
-
DEFINIZIONI
|
|
110
|
-
${def.join("\n")}
|
|
111
|
-
|
|
112
|
-
RELAZIONI
|
|
113
|
-
${rel}
|
|
114
|
-
|
|
106
|
+
const body = `INDICE
|
|
107
|
+
${uniqTerms.map((t, i) => `${i + 1} = ${t}`).join("\n")}
|
|
108
|
+
|
|
109
|
+
DEFINIZIONI
|
|
110
|
+
${def.join("\n")}
|
|
111
|
+
|
|
112
|
+
RELAZIONI
|
|
113
|
+
${rel}
|
|
114
|
+
|
|
115
115
|
OUTPUT: ${where} ${action} (status=${event.status}); ${lesson}; richiamabile via recall.`;
|
|
116
116
|
return {
|
|
117
117
|
room,
|
package/dist/vascend/vision.js
CHANGED
|
@@ -353,23 +353,23 @@ export function buildVisionBlock(v) {
|
|
|
353
353
|
.filter(Boolean)
|
|
354
354
|
.join(" ");
|
|
355
355
|
const comp = visionCompleteness(v);
|
|
356
|
-
return `[VASCEND_VISION]
|
|
357
|
-
INDICE
|
|
358
|
-
1 = obiettivo
|
|
359
|
-
2 = valore
|
|
360
|
-
3 = funzionalita
|
|
361
|
-
4 = pronto-produzione
|
|
362
|
-
|
|
363
|
-
DEFINIZIONI
|
|
364
|
-
@obiettivo[finale]: "${q(v.goal)}"
|
|
365
|
-
@valore[perche]: "${q(valLine || "(non specificato)")}"
|
|
366
|
-
${featDefs.length ? featDefs.join("\n") : "@funzionalita[nessuna]: -"}
|
|
367
|
-
${rpDefs.length ? rpDefs.join("\n") : "@pronto[nessun-criterio]: -"}
|
|
368
|
-
|
|
369
|
-
RELAZIONI
|
|
370
|
-
@R-goal: @obiettivo[finale] -> @valore[perche] [ il lavoro esiste per realizzare questo valore ]
|
|
371
|
-
@R-feat: funzionalita[CORE] -> @obiettivo[finale] [ le core sono indispensabili all'obiettivo ]
|
|
372
|
-
@R-rp: @pronto -> consegna [ spedibile sse ogni CORE e' built e ogni criterio e' MET ]
|
|
373
|
-
|
|
356
|
+
return `[VASCEND_VISION]
|
|
357
|
+
INDICE
|
|
358
|
+
1 = obiettivo
|
|
359
|
+
2 = valore
|
|
360
|
+
3 = funzionalita
|
|
361
|
+
4 = pronto-produzione
|
|
362
|
+
|
|
363
|
+
DEFINIZIONI
|
|
364
|
+
@obiettivo[finale]: "${q(v.goal)}"
|
|
365
|
+
@valore[perche]: "${q(valLine || "(non specificato)")}"
|
|
366
|
+
${featDefs.length ? featDefs.join("\n") : "@funzionalita[nessuna]: -"}
|
|
367
|
+
${rpDefs.length ? rpDefs.join("\n") : "@pronto[nessun-criterio]: -"}
|
|
368
|
+
|
|
369
|
+
RELAZIONI
|
|
370
|
+
@R-goal: @obiettivo[finale] -> @valore[perche] [ il lavoro esiste per realizzare questo valore ]
|
|
371
|
+
@R-feat: funzionalita[CORE] -> @obiettivo[finale] [ le core sono indispensabili all'obiettivo ]
|
|
372
|
+
@R-rp: @pronto -> consegna [ spedibile sse ogni CORE e' built e ogni criterio e' MET ]
|
|
373
|
+
|
|
374
374
|
OUTPUT: questa e' la Vision condivisa del task; pronto-per-produzione=${comp.productionReady} (core ${comp.coreBuilt}/${comp.coreTotal}, readiness ${comp.readinessMet}/${comp.readinessTotal}).`;
|
|
375
375
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Backend Computer Use incorporato
|
|
2
|
-
|
|
3
|
-
Snapshot dei moduli nativi del workspace Vascend Horizon (`vascend-terminal/src/main`), incorporato per rendere Talos CLI indipendente da Electron e dal repository esterno.
|
|
4
|
-
|
|
5
|
-
I file JavaScript mantengono CommonJS e il codice upstream. Dipendenze: soltanto built-in Node e PowerShell/Windows UI Automation. Non aggiungere dipendenze runtime. Il consenso, la serializzazione, l'isolamento sessioni e la conversione immagini sono gestiti da `src/tools/computer-use.ts`. I tool RAC che richiedono l'host non vengono esposti.
|
|
6
|
-
|
|
7
|
-
`node scripts/copy-computer-use.mjs` copia questi asset in `dist/vendor/computer-use` durante la build. Aggiornare lo snapshot in modo esplicito, rivedendo dipendenze e test; nessun caricamento dal repository fratello in produzione.
|
|
1
|
+
# Backend Computer Use incorporato
|
|
2
|
+
|
|
3
|
+
Snapshot dei moduli nativi del workspace Vascend Horizon (`vascend-terminal/src/main`), incorporato per rendere Talos CLI indipendente da Electron e dal repository esterno.
|
|
4
|
+
|
|
5
|
+
I file JavaScript mantengono CommonJS e il codice upstream. Dipendenze: soltanto built-in Node e PowerShell/Windows UI Automation. Non aggiungere dipendenze runtime. Il consenso, la serializzazione, l'isolamento sessioni e la conversione immagini sono gestiti da `src/tools/computer-use.ts`. I tool RAC che richiedono l'host non vengono esposti.
|
|
6
|
+
|
|
7
|
+
`node scripts/copy-computer-use.mjs` copia questi asset in `dist/vendor/computer-use` durante la build. Aggiornare lo snapshot in modo esplicito, rivedendo dipendenze e test; nessun caricamento dal repository fratello in produzione.
|
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Inventario del desktop per Computer Use: applicazioni, processi e finestre.
|
|
4
|
-
// Sta fuori dal modulo dei tool perche' e' una fonte dati con regole proprie
|
|
5
|
-
// (fusione di due enumerazioni Win32, icone locali che non devono mai entrare
|
|
6
|
-
// nel contesto del modello, fotografia breve riusata da processes e attach).
|
|
7
|
-
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
const { integer, cleanWindow, cleanProcess } = require('./computer-observe');
|
|
10
|
-
|
|
11
|
-
const KNOWN_APPLICATIONS = Object.freeze({
|
|
12
|
-
claude: 'Claude Desktop', code: 'Visual Studio Code', chrome: 'Google Chrome',
|
|
13
|
-
msedge: 'Microsoft Edge', firefox: 'Firefox', slack: 'Slack', teams: 'Microsoft Teams',
|
|
14
|
-
notion: 'Notion', discord: 'Discord', spotify: 'Spotify', explorer: 'Esplora file',
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
function applicationName(process, windows) {
|
|
18
|
-
const normalized = String(process || '').trim().toLocaleLowerCase();
|
|
19
|
-
if (KNOWN_APPLICATIONS[normalized]) return KNOWN_APPLICATIONS[normalized];
|
|
20
|
-
const titled = (Array.isArray(windows) ? windows : []).find((row) => row.visible && row.title)
|
|
21
|
-
|| (Array.isArray(windows) ? windows : []).find((row) => row.title);
|
|
22
|
-
if (titled && titled.title) return titled.title.slice(0, 120);
|
|
23
|
-
const raw = String(process || 'Applicazione').replace(/[-_]+/g, ' ').trim();
|
|
24
|
-
return raw ? raw.charAt(0).toLocaleUpperCase() + raw.slice(1) : 'Applicazione';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function safeIconDataUrl(value) {
|
|
28
|
-
const icon = String(value || '');
|
|
29
|
-
return icon.length <= 32768 && /^data:image\/png;base64,[a-z0-9+/=]+$/i.test(icon) ? icon : '';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Il percorso dell'eseguibile serve solo all'host per estrarre l'icona: non
|
|
33
|
-
// lascia mai questo processo.
|
|
34
|
-
function publicProcess(value) {
|
|
35
|
-
const { _executablePath, ...safe } = value && typeof value === 'object' ? value : {};
|
|
36
|
-
return safe;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function publicApplication(value) {
|
|
40
|
-
const { icon, _iconProcess, ...safe } = value && typeof value === 'object' ? value : {};
|
|
41
|
-
return safe;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function uiApplication(value) {
|
|
45
|
-
const { _iconProcess, ...safe } = value && typeof value === 'object' ? value : {};
|
|
46
|
-
return safe;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function publicTarget(value) {
|
|
50
|
-
const { icon, _iconProcess, ...safe } = value && typeof value === 'object' ? value : {};
|
|
51
|
-
return Object.keys(safe).length ? safe : null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function mergeProcessWindows(windows, processes) {
|
|
55
|
-
const byId = new Map((Array.isArray(windows) ? windows : []).map((row) => [row.windowId, row]));
|
|
56
|
-
for (const process of Array.isArray(processes) ? processes : []) {
|
|
57
|
-
if (!process.windowId || !process.bounds || byId.has(process.windowId)) continue;
|
|
58
|
-
const row = cleanWindow({
|
|
59
|
-
windowId: process.windowId,
|
|
60
|
-
processId: process.processId,
|
|
61
|
-
process: process.process,
|
|
62
|
-
title: process.title || process.process,
|
|
63
|
-
bounds: process.bounds,
|
|
64
|
-
visible: process.visible,
|
|
65
|
-
minimized: process.minimized,
|
|
66
|
-
});
|
|
67
|
-
if (row) byId.set(row.windowId, row);
|
|
68
|
-
}
|
|
69
|
-
return [...byId.values()].sort((a, b) => Number(b.visible) - Number(a.visible)
|
|
70
|
-
|| Number(a.minimized) - Number(b.minimized)
|
|
71
|
-
|| a.process.localeCompare(b.process) || a.title.localeCompare(b.title)).slice(0, 240);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function createDesktopInventory({ listWindows, listProcesses, resolveProcessIcon, cacheMs = 2500 } = {}) {
|
|
75
|
-
let cache = null;
|
|
76
|
-
const iconCache = new Map();
|
|
77
|
-
|
|
78
|
-
const windowsNow = async () => {
|
|
79
|
-
let rows = [];
|
|
80
|
-
try { rows = await listWindows(); } catch (_) { rows = []; }
|
|
81
|
-
return (Array.isArray(rows) ? rows : []).map(cleanWindow).filter(Boolean).slice(0, 240);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const processesNow = async (windows) => {
|
|
85
|
-
let rows = [];
|
|
86
|
-
try { rows = listProcesses ? await listProcesses() : windows; } catch (_) { rows = []; }
|
|
87
|
-
const byId = new Map();
|
|
88
|
-
for (const raw of Array.isArray(rows) ? rows : []) {
|
|
89
|
-
const row = cleanProcess(raw);
|
|
90
|
-
if (row) byId.set(row.processId, row);
|
|
91
|
-
}
|
|
92
|
-
// EnumWindows e' piu autorevole sul collegamento tra HWND e PID: fonde le
|
|
93
|
-
// due fonti e promuove in cima le applicazioni realmente collegabili.
|
|
94
|
-
for (const win of Array.isArray(windows) ? windows : []) {
|
|
95
|
-
const previous = byId.get(win.processId);
|
|
96
|
-
byId.set(win.processId, {
|
|
97
|
-
processId: win.processId,
|
|
98
|
-
process: win.process || previous && previous.process || 'processo',
|
|
99
|
-
title: win.title || previous && previous.title || '',
|
|
100
|
-
windowId: win.windowId,
|
|
101
|
-
hasWindow: true,
|
|
102
|
-
bounds: win.bounds,
|
|
103
|
-
visible: win.visible,
|
|
104
|
-
minimized: win.minimized,
|
|
105
|
-
responding: previous ? previous.responding : true,
|
|
106
|
-
_executablePath: previous ? previous._executablePath : '',
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
return [...byId.values()].sort((a, b) => Number(b.hasWindow) - Number(a.hasWindow)
|
|
110
|
-
|| Number(b.visible) - Number(a.visible)
|
|
111
|
-
|| a.process.localeCompare(b.process) || a.processId - b.processId).slice(0, 512);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const applicationsNow = async (processes, windows) => {
|
|
115
|
-
const groups = new Map();
|
|
116
|
-
for (const process of Array.isArray(processes) ? processes : []) {
|
|
117
|
-
const key = String(process.process || '').trim().toLocaleLowerCase();
|
|
118
|
-
if (!key) continue;
|
|
119
|
-
if (!groups.has(key)) groups.set(key, { process: process.process, processes: [], windows: [] });
|
|
120
|
-
groups.get(key).processes.push(process);
|
|
121
|
-
}
|
|
122
|
-
for (const win of Array.isArray(windows) ? windows : []) {
|
|
123
|
-
const key = String(win.process || '').trim().toLocaleLowerCase();
|
|
124
|
-
if (!key) continue;
|
|
125
|
-
if (!groups.has(key)) groups.set(key, { process: win.process, processes: [], windows: [] });
|
|
126
|
-
groups.get(key).windows.push(win);
|
|
127
|
-
}
|
|
128
|
-
const applications = [...groups.entries()].map(([key, group]) => {
|
|
129
|
-
const processIds = [...new Set(group.processes.map((row) => row.processId)
|
|
130
|
-
.concat(group.windows.map((row) => row.processId)).filter(Boolean))];
|
|
131
|
-
const windowIds = [...new Set(group.windows.map((row) => row.windowId).filter(Boolean))];
|
|
132
|
-
const titles = [...new Set(group.windows.map((row) => row.title).filter(Boolean))].slice(0, 8);
|
|
133
|
-
return {
|
|
134
|
-
applicationId: 'app-' + crypto.createHash('sha256').update(key).digest('hex').slice(0, 10),
|
|
135
|
-
name: applicationName(group.process, group.windows),
|
|
136
|
-
process: group.process,
|
|
137
|
-
processIds,
|
|
138
|
-
instanceCount: processIds.length,
|
|
139
|
-
windowIds,
|
|
140
|
-
windowCount: windowIds.length,
|
|
141
|
-
titles,
|
|
142
|
-
controllable: windowIds.length > 0,
|
|
143
|
-
visible: group.windows.some((row) => row.visible),
|
|
144
|
-
minimized: group.windows.some((row) => row.minimized),
|
|
145
|
-
_iconProcess: group.processes.find((row) => row.hasWindow) || group.processes[0] || null,
|
|
146
|
-
};
|
|
147
|
-
}).sort((a, b) => Number(b.controllable) - Number(a.controllable)
|
|
148
|
-
|| Number(b.visible) - Number(a.visible)
|
|
149
|
-
|| a.name.localeCompare(b.name)).slice(0, 240);
|
|
150
|
-
return applications;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// Estrarre l'icona di ogni processo richiede accesso al relativo eseguibile e
|
|
154
|
-
// rendeva computer_processes lento anche quando l'utente cercava una sola app.
|
|
155
|
-
// Risolviamo soltanto le applicazioni effettivamente mostrate/collegate e
|
|
156
|
-
// riutilizziamo il risultato per tutto il run.
|
|
157
|
-
const hydrateApplicationIcons = async (rows) => {
|
|
158
|
-
if (!resolveProcessIcon) return rows;
|
|
159
|
-
await Promise.all((Array.isArray(rows) ? rows : []).map(async (application) => {
|
|
160
|
-
if (!application || application.icon || !application._iconProcess) return;
|
|
161
|
-
const process = application._iconProcess;
|
|
162
|
-
const key = String(process._executablePath || process.process || process.processId || '').toLocaleLowerCase();
|
|
163
|
-
if (key && iconCache.has(key)) {
|
|
164
|
-
application.icon = iconCache.get(key);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
let icon = '';
|
|
168
|
-
try { icon = safeIconDataUrl(await resolveProcessIcon(process)); } catch (_) { icon = ''; }
|
|
169
|
-
application.icon = icon;
|
|
170
|
-
if (key) iconCache.set(key, icon);
|
|
171
|
-
}));
|
|
172
|
-
return rows;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// computer_processes e computer_attach sono normalmente consecutivi. Una
|
|
176
|
-
// seconda enumerazione Win32/PowerShell immediata aggiungeva vari secondi
|
|
177
|
-
// senza aumentare la precisione; riusiamo solo una fotografia molto breve.
|
|
178
|
-
const targetsNow = async (progress) => {
|
|
179
|
-
if (cache && Date.now() - cache.capturedAt < integer(cacheMs, 0, 60000, 2500)) {
|
|
180
|
-
if (typeof progress === 'function') progress({ phase: 'windows', windows: cache.value.windows });
|
|
181
|
-
return cache.value;
|
|
182
|
-
}
|
|
183
|
-
const enumerated = await windowsNow();
|
|
184
|
-
if (typeof progress === 'function') progress({ phase: 'windows', windows: enumerated });
|
|
185
|
-
const processes = await processesNow(enumerated);
|
|
186
|
-
const windows = mergeProcessWindows(enumerated, processes);
|
|
187
|
-
const applications = await applicationsNow(processes, windows);
|
|
188
|
-
const value = { applications, processes: processes.map(publicProcess), windows };
|
|
189
|
-
cache = { capturedAt: Date.now(), value };
|
|
190
|
-
return value;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
windowsNow,
|
|
195
|
-
targetsNow,
|
|
196
|
-
hydrateApplicationIcons,
|
|
197
|
-
invalidate: () => { cache = null; },
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
module.exports = {
|
|
202
|
-
KNOWN_APPLICATIONS,
|
|
203
|
-
applicationName,
|
|
204
|
-
safeIconDataUrl,
|
|
205
|
-
publicProcess,
|
|
206
|
-
publicApplication,
|
|
207
|
-
uiApplication,
|
|
208
|
-
publicTarget,
|
|
209
|
-
mergeProcessWindows,
|
|
210
|
-
createDesktopInventory,
|
|
211
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Inventario del desktop per Computer Use: applicazioni, processi e finestre.
|
|
4
|
+
// Sta fuori dal modulo dei tool perche' e' una fonte dati con regole proprie
|
|
5
|
+
// (fusione di due enumerazioni Win32, icone locali che non devono mai entrare
|
|
6
|
+
// nel contesto del modello, fotografia breve riusata da processes e attach).
|
|
7
|
+
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const { integer, cleanWindow, cleanProcess } = require('./computer-observe');
|
|
10
|
+
|
|
11
|
+
const KNOWN_APPLICATIONS = Object.freeze({
|
|
12
|
+
claude: 'Claude Desktop', code: 'Visual Studio Code', chrome: 'Google Chrome',
|
|
13
|
+
msedge: 'Microsoft Edge', firefox: 'Firefox', slack: 'Slack', teams: 'Microsoft Teams',
|
|
14
|
+
notion: 'Notion', discord: 'Discord', spotify: 'Spotify', explorer: 'Esplora file',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function applicationName(process, windows) {
|
|
18
|
+
const normalized = String(process || '').trim().toLocaleLowerCase();
|
|
19
|
+
if (KNOWN_APPLICATIONS[normalized]) return KNOWN_APPLICATIONS[normalized];
|
|
20
|
+
const titled = (Array.isArray(windows) ? windows : []).find((row) => row.visible && row.title)
|
|
21
|
+
|| (Array.isArray(windows) ? windows : []).find((row) => row.title);
|
|
22
|
+
if (titled && titled.title) return titled.title.slice(0, 120);
|
|
23
|
+
const raw = String(process || 'Applicazione').replace(/[-_]+/g, ' ').trim();
|
|
24
|
+
return raw ? raw.charAt(0).toLocaleUpperCase() + raw.slice(1) : 'Applicazione';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function safeIconDataUrl(value) {
|
|
28
|
+
const icon = String(value || '');
|
|
29
|
+
return icon.length <= 32768 && /^data:image\/png;base64,[a-z0-9+/=]+$/i.test(icon) ? icon : '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Il percorso dell'eseguibile serve solo all'host per estrarre l'icona: non
|
|
33
|
+
// lascia mai questo processo.
|
|
34
|
+
function publicProcess(value) {
|
|
35
|
+
const { _executablePath, ...safe } = value && typeof value === 'object' ? value : {};
|
|
36
|
+
return safe;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function publicApplication(value) {
|
|
40
|
+
const { icon, _iconProcess, ...safe } = value && typeof value === 'object' ? value : {};
|
|
41
|
+
return safe;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function uiApplication(value) {
|
|
45
|
+
const { _iconProcess, ...safe } = value && typeof value === 'object' ? value : {};
|
|
46
|
+
return safe;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function publicTarget(value) {
|
|
50
|
+
const { icon, _iconProcess, ...safe } = value && typeof value === 'object' ? value : {};
|
|
51
|
+
return Object.keys(safe).length ? safe : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function mergeProcessWindows(windows, processes) {
|
|
55
|
+
const byId = new Map((Array.isArray(windows) ? windows : []).map((row) => [row.windowId, row]));
|
|
56
|
+
for (const process of Array.isArray(processes) ? processes : []) {
|
|
57
|
+
if (!process.windowId || !process.bounds || byId.has(process.windowId)) continue;
|
|
58
|
+
const row = cleanWindow({
|
|
59
|
+
windowId: process.windowId,
|
|
60
|
+
processId: process.processId,
|
|
61
|
+
process: process.process,
|
|
62
|
+
title: process.title || process.process,
|
|
63
|
+
bounds: process.bounds,
|
|
64
|
+
visible: process.visible,
|
|
65
|
+
minimized: process.minimized,
|
|
66
|
+
});
|
|
67
|
+
if (row) byId.set(row.windowId, row);
|
|
68
|
+
}
|
|
69
|
+
return [...byId.values()].sort((a, b) => Number(b.visible) - Number(a.visible)
|
|
70
|
+
|| Number(a.minimized) - Number(b.minimized)
|
|
71
|
+
|| a.process.localeCompare(b.process) || a.title.localeCompare(b.title)).slice(0, 240);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createDesktopInventory({ listWindows, listProcesses, resolveProcessIcon, cacheMs = 2500 } = {}) {
|
|
75
|
+
let cache = null;
|
|
76
|
+
const iconCache = new Map();
|
|
77
|
+
|
|
78
|
+
const windowsNow = async () => {
|
|
79
|
+
let rows = [];
|
|
80
|
+
try { rows = await listWindows(); } catch (_) { rows = []; }
|
|
81
|
+
return (Array.isArray(rows) ? rows : []).map(cleanWindow).filter(Boolean).slice(0, 240);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const processesNow = async (windows) => {
|
|
85
|
+
let rows = [];
|
|
86
|
+
try { rows = listProcesses ? await listProcesses() : windows; } catch (_) { rows = []; }
|
|
87
|
+
const byId = new Map();
|
|
88
|
+
for (const raw of Array.isArray(rows) ? rows : []) {
|
|
89
|
+
const row = cleanProcess(raw);
|
|
90
|
+
if (row) byId.set(row.processId, row);
|
|
91
|
+
}
|
|
92
|
+
// EnumWindows e' piu autorevole sul collegamento tra HWND e PID: fonde le
|
|
93
|
+
// due fonti e promuove in cima le applicazioni realmente collegabili.
|
|
94
|
+
for (const win of Array.isArray(windows) ? windows : []) {
|
|
95
|
+
const previous = byId.get(win.processId);
|
|
96
|
+
byId.set(win.processId, {
|
|
97
|
+
processId: win.processId,
|
|
98
|
+
process: win.process || previous && previous.process || 'processo',
|
|
99
|
+
title: win.title || previous && previous.title || '',
|
|
100
|
+
windowId: win.windowId,
|
|
101
|
+
hasWindow: true,
|
|
102
|
+
bounds: win.bounds,
|
|
103
|
+
visible: win.visible,
|
|
104
|
+
minimized: win.minimized,
|
|
105
|
+
responding: previous ? previous.responding : true,
|
|
106
|
+
_executablePath: previous ? previous._executablePath : '',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return [...byId.values()].sort((a, b) => Number(b.hasWindow) - Number(a.hasWindow)
|
|
110
|
+
|| Number(b.visible) - Number(a.visible)
|
|
111
|
+
|| a.process.localeCompare(b.process) || a.processId - b.processId).slice(0, 512);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const applicationsNow = async (processes, windows) => {
|
|
115
|
+
const groups = new Map();
|
|
116
|
+
for (const process of Array.isArray(processes) ? processes : []) {
|
|
117
|
+
const key = String(process.process || '').trim().toLocaleLowerCase();
|
|
118
|
+
if (!key) continue;
|
|
119
|
+
if (!groups.has(key)) groups.set(key, { process: process.process, processes: [], windows: [] });
|
|
120
|
+
groups.get(key).processes.push(process);
|
|
121
|
+
}
|
|
122
|
+
for (const win of Array.isArray(windows) ? windows : []) {
|
|
123
|
+
const key = String(win.process || '').trim().toLocaleLowerCase();
|
|
124
|
+
if (!key) continue;
|
|
125
|
+
if (!groups.has(key)) groups.set(key, { process: win.process, processes: [], windows: [] });
|
|
126
|
+
groups.get(key).windows.push(win);
|
|
127
|
+
}
|
|
128
|
+
const applications = [...groups.entries()].map(([key, group]) => {
|
|
129
|
+
const processIds = [...new Set(group.processes.map((row) => row.processId)
|
|
130
|
+
.concat(group.windows.map((row) => row.processId)).filter(Boolean))];
|
|
131
|
+
const windowIds = [...new Set(group.windows.map((row) => row.windowId).filter(Boolean))];
|
|
132
|
+
const titles = [...new Set(group.windows.map((row) => row.title).filter(Boolean))].slice(0, 8);
|
|
133
|
+
return {
|
|
134
|
+
applicationId: 'app-' + crypto.createHash('sha256').update(key).digest('hex').slice(0, 10),
|
|
135
|
+
name: applicationName(group.process, group.windows),
|
|
136
|
+
process: group.process,
|
|
137
|
+
processIds,
|
|
138
|
+
instanceCount: processIds.length,
|
|
139
|
+
windowIds,
|
|
140
|
+
windowCount: windowIds.length,
|
|
141
|
+
titles,
|
|
142
|
+
controllable: windowIds.length > 0,
|
|
143
|
+
visible: group.windows.some((row) => row.visible),
|
|
144
|
+
minimized: group.windows.some((row) => row.minimized),
|
|
145
|
+
_iconProcess: group.processes.find((row) => row.hasWindow) || group.processes[0] || null,
|
|
146
|
+
};
|
|
147
|
+
}).sort((a, b) => Number(b.controllable) - Number(a.controllable)
|
|
148
|
+
|| Number(b.visible) - Number(a.visible)
|
|
149
|
+
|| a.name.localeCompare(b.name)).slice(0, 240);
|
|
150
|
+
return applications;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Estrarre l'icona di ogni processo richiede accesso al relativo eseguibile e
|
|
154
|
+
// rendeva computer_processes lento anche quando l'utente cercava una sola app.
|
|
155
|
+
// Risolviamo soltanto le applicazioni effettivamente mostrate/collegate e
|
|
156
|
+
// riutilizziamo il risultato per tutto il run.
|
|
157
|
+
const hydrateApplicationIcons = async (rows) => {
|
|
158
|
+
if (!resolveProcessIcon) return rows;
|
|
159
|
+
await Promise.all((Array.isArray(rows) ? rows : []).map(async (application) => {
|
|
160
|
+
if (!application || application.icon || !application._iconProcess) return;
|
|
161
|
+
const process = application._iconProcess;
|
|
162
|
+
const key = String(process._executablePath || process.process || process.processId || '').toLocaleLowerCase();
|
|
163
|
+
if (key && iconCache.has(key)) {
|
|
164
|
+
application.icon = iconCache.get(key);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let icon = '';
|
|
168
|
+
try { icon = safeIconDataUrl(await resolveProcessIcon(process)); } catch (_) { icon = ''; }
|
|
169
|
+
application.icon = icon;
|
|
170
|
+
if (key) iconCache.set(key, icon);
|
|
171
|
+
}));
|
|
172
|
+
return rows;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// computer_processes e computer_attach sono normalmente consecutivi. Una
|
|
176
|
+
// seconda enumerazione Win32/PowerShell immediata aggiungeva vari secondi
|
|
177
|
+
// senza aumentare la precisione; riusiamo solo una fotografia molto breve.
|
|
178
|
+
const targetsNow = async (progress) => {
|
|
179
|
+
if (cache && Date.now() - cache.capturedAt < integer(cacheMs, 0, 60000, 2500)) {
|
|
180
|
+
if (typeof progress === 'function') progress({ phase: 'windows', windows: cache.value.windows });
|
|
181
|
+
return cache.value;
|
|
182
|
+
}
|
|
183
|
+
const enumerated = await windowsNow();
|
|
184
|
+
if (typeof progress === 'function') progress({ phase: 'windows', windows: enumerated });
|
|
185
|
+
const processes = await processesNow(enumerated);
|
|
186
|
+
const windows = mergeProcessWindows(enumerated, processes);
|
|
187
|
+
const applications = await applicationsNow(processes, windows);
|
|
188
|
+
const value = { applications, processes: processes.map(publicProcess), windows };
|
|
189
|
+
cache = { capturedAt: Date.now(), value };
|
|
190
|
+
return value;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
windowsNow,
|
|
195
|
+
targetsNow,
|
|
196
|
+
hydrateApplicationIcons,
|
|
197
|
+
invalidate: () => { cache = null; },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
KNOWN_APPLICATIONS,
|
|
203
|
+
applicationName,
|
|
204
|
+
safeIconDataUrl,
|
|
205
|
+
publicProcess,
|
|
206
|
+
publicApplication,
|
|
207
|
+
uiApplication,
|
|
208
|
+
publicTarget,
|
|
209
|
+
mergeProcessWindows,
|
|
210
|
+
createDesktopInventory,
|
|
211
|
+
};
|