skopix 2.0.0 → 2.0.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/cli/commands/agent.js +1 -1
- package/cli/commands/dashboard.js +63 -45
- package/core/llm.js +3 -2
- package/package.json +1 -1
package/cli/commands/agent.js
CHANGED
|
@@ -220,7 +220,7 @@ export async function agentCommand(options) {
|
|
|
220
220
|
|
|
221
221
|
try {
|
|
222
222
|
const { chromium } = await import('playwright');
|
|
223
|
-
const sessionDir = path.join(os.homedir(), '.skopix',
|
|
223
|
+
const sessionDir = path.join(os.homedir(), '.skopix', runId);
|
|
224
224
|
await fs.ensureDir(sessionDir);
|
|
225
225
|
|
|
226
226
|
send({ type: 'stdout', text: '' });
|
|
@@ -19,9 +19,9 @@ const SAVED_TESTS_FILE = '_saved.suite.yaml';
|
|
|
19
19
|
export async function dashboardCommand(options) {
|
|
20
20
|
const port = parseInt(options.port) || 9000;
|
|
21
21
|
const host = options.host || process.env.SKOPIX_HOST || '127.0.0.1';
|
|
22
|
-
const reportsDir = path.resolve(options.dir || '
|
|
22
|
+
const reportsDir = path.resolve(options.dir || path.join(os.homedir(), '.skopix'));
|
|
23
23
|
const webRoot = path.resolve(__dirname, '..', '..', 'web');
|
|
24
|
-
const suitesDir = path.
|
|
24
|
+
const suitesDir = path.join(os.homedir(), '.skopix');
|
|
25
25
|
const suiteRunsDir = path.join(reportsDir, '.suite-runs');
|
|
26
26
|
|
|
27
27
|
await fs.ensureDir(reportsDir);
|
|
@@ -1889,6 +1889,13 @@ export async function dashboardCommand(options) {
|
|
|
1889
1889
|
ws.send(JSON.stringify({ type: 'registered', agentId }));
|
|
1890
1890
|
console.log(chalk.cyan('[agent]') + ' Connected: ' + agent.name + ' (' + agent.machine + ')');
|
|
1891
1891
|
broadcastAgentList();
|
|
1892
|
+
|
|
1893
|
+
// Keepalive ping every 30s to prevent idle disconnection
|
|
1894
|
+
const pingInterval = setInterval(() => {
|
|
1895
|
+
if (ws.readyState !== 1) { clearInterval(pingInterval); return; }
|
|
1896
|
+
try { ws.send(JSON.stringify({ type: 'ping' })); } catch { clearInterval(pingInterval); }
|
|
1897
|
+
}, 30000);
|
|
1898
|
+
ws.on('close', () => clearInterval(pingInterval));
|
|
1892
1899
|
return;
|
|
1893
1900
|
}
|
|
1894
1901
|
|
|
@@ -2110,55 +2117,64 @@ function testIdFromName(name) {
|
|
|
2110
2117
|
|
|
2111
2118
|
// ─── SESSIONS ─────────────────────────────────────────────────────────────────
|
|
2112
2119
|
async function listSessions(reportsDir) {
|
|
2113
|
-
if (!await fs.pathExists(reportsDir)) return [];
|
|
2114
|
-
const entries = await fs.readdir(reportsDir);
|
|
2120
|
+
if (!await fs.pathExists(reportsDir)) { process.stderr.write('[sessions] reportsDir not found: ' + reportsDir + '\n'); return []; }
|
|
2115
2121
|
const sessions = [];
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2122
|
+
// Check both reportsDir directly and reportsDir/sessions subfolder
|
|
2123
|
+
const dirsToCheck = [reportsDir, path.join(reportsDir, 'sessions')];
|
|
2124
|
+
for (const dir of dirsToCheck) {
|
|
2125
|
+
if (!await fs.pathExists(dir)) continue;
|
|
2126
|
+
const entries = await fs.readdir(dir);
|
|
2127
|
+
process.stderr.write('[sessions] scanning ' + dir + ' entries: ' + entries.join(',') + '\n');
|
|
2128
|
+
for (const entry of entries) {
|
|
2129
|
+
if (entry.startsWith('.')) continue;
|
|
2130
|
+
const sessionPath = path.join(dir, entry);
|
|
2131
|
+
const stat = await fs.stat(sessionPath);
|
|
2132
|
+
if (!stat.isDirectory()) continue;
|
|
2133
|
+
const jsonPath = path.join(sessionPath, 'report.json');
|
|
2134
|
+
if (await fs.pathExists(jsonPath)) {
|
|
2135
|
+
try {
|
|
2136
|
+
const data = await fs.readJson(jsonPath);
|
|
2137
|
+
const status = data.goalAchieved ? 'passed' : data.stuck ? 'stuck' : 'failed';
|
|
2138
|
+
let runBy = null;
|
|
2139
|
+
const runByPath = path.join(sessionPath, 'runBy.json');
|
|
2140
|
+
if (await fs.pathExists(runByPath)) {
|
|
2141
|
+
try { runBy = await fs.readJson(runByPath); } catch {}
|
|
2142
|
+
}
|
|
2143
|
+
sessions.push({
|
|
2144
|
+
id: data.sessionId || entry, status,
|
|
2145
|
+
url: data.url, goal: data.goal,
|
|
2146
|
+
steps: data.steps?.length || 0,
|
|
2147
|
+
issues: data.issues?.length || 0,
|
|
2148
|
+
duration: formatDuration(data.duration || 0),
|
|
2149
|
+
durationMs: data.duration || 0,
|
|
2150
|
+
when: relativeTime(stat.mtime),
|
|
2151
|
+
mtime: stat.mtime.toISOString(),
|
|
2152
|
+
model: data.model, provider: data.provider,
|
|
2153
|
+
runBy,
|
|
2154
|
+
});
|
|
2155
|
+
} catch {}
|
|
2156
|
+
}
|
|
2145
2157
|
}
|
|
2146
2158
|
}
|
|
2147
2159
|
sessions.sort((a, b) => new Date(b.mtime) - new Date(a.mtime));
|
|
2148
2160
|
return sessions;
|
|
2149
2161
|
}
|
|
2150
2162
|
async function getSession(reportsDir, id) {
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2163
|
+
// Check both reportsDir/id and reportsDir/sessions/id
|
|
2164
|
+
const locations = [path.join(reportsDir, id), path.join(reportsDir, 'sessions', id)];
|
|
2165
|
+
for (const sessionPath of locations) {
|
|
2166
|
+
const jsonPath = path.join(sessionPath, 'report.json');
|
|
2167
|
+
if (!await fs.pathExists(jsonPath)) continue;
|
|
2168
|
+
try {
|
|
2169
|
+
const data = await fs.readJson(jsonPath);
|
|
2170
|
+
const runByPath = path.join(sessionPath, 'runBy.json');
|
|
2171
|
+
if (await fs.pathExists(runByPath)) {
|
|
2172
|
+
try { data.runBy = await fs.readJson(runByPath); } catch {}
|
|
2173
|
+
}
|
|
2174
|
+
return data;
|
|
2175
|
+
} catch {}
|
|
2176
|
+
}
|
|
2177
|
+
return null;
|
|
2162
2178
|
}
|
|
2163
2179
|
function computeStats(sessions) {
|
|
2164
2180
|
const total = sessions.length;
|
|
@@ -2173,7 +2189,7 @@ function computeStats(sessions) {
|
|
|
2173
2189
|
return { total, passed, failed, stuck, passRate, totalIssues, avgDuration: formatDuration(avgMs), avgDurationMs: avgMs, thisWeek };
|
|
2174
2190
|
}
|
|
2175
2191
|
async function getConfig() {
|
|
2176
|
-
const envPath = path.
|
|
2192
|
+
const envPath = path.join(os.homedir(), '.skopix.env');
|
|
2177
2193
|
if (!await fs.pathExists(envPath)) return [];
|
|
2178
2194
|
const content = await fs.readFile(envPath, 'utf-8');
|
|
2179
2195
|
const config = [];
|
|
@@ -3388,6 +3404,7 @@ async function deleteAllSuiteRuns(suiteRunsDir) {
|
|
|
3388
3404
|
async function closeRemoteIssue(issue) {
|
|
3389
3405
|
const axios = (await import('axios')).default;
|
|
3390
3406
|
const dotenv = (await import('dotenv')).default;
|
|
3407
|
+
dotenv.config({ path: path.join(os.homedir(), '.skopix.env') });
|
|
3391
3408
|
dotenv.config({ path: path.resolve(process.cwd(), '.skopix.env') });
|
|
3392
3409
|
dotenv.config();
|
|
3393
3410
|
|
|
@@ -3470,6 +3487,7 @@ async function resolveUserSecretsEnv(userId, teamMode) {
|
|
|
3470
3487
|
async function syncIssuesStatus() {
|
|
3471
3488
|
const axios = (await import('axios')).default;
|
|
3472
3489
|
const dotenv = (await import('dotenv')).default;
|
|
3490
|
+
dotenv.config({ path: path.join(os.homedir(), '.skopix.env') });
|
|
3473
3491
|
dotenv.config({ path: path.resolve(process.cwd(), '.skopix.env') });
|
|
3474
3492
|
dotenv.config();
|
|
3475
3493
|
|
package/core/llm.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import dotenv from 'dotenv';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import axios from 'axios';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
dotenv.config({ path:
|
|
6
|
+
// Always load from home directory
|
|
7
|
+
dotenv.config({ path: path.join(os.homedir(), '.skopix.env') });
|
|
7
8
|
dotenv.config();
|
|
8
9
|
|
|
9
10
|
export class LLMRouter {
|
package/package.json
CHANGED