@supen-ai/cli 0.1.10 → 0.1.11
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 +8 -6
- package/daemon/dist/agent-sdk/driver-output-ui.js +1 -1
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js +1 -1
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js.map +1 -1
- package/daemon/dist/agent-sdk/drivers/registry.js +1 -1
- package/daemon/dist/agent-sdk/drivers/registry.js.map +1 -1
- package/daemon/dist/channels/http-routes.js +59 -59
- package/daemon/dist/channels/http-routes.js.map +1 -1
- package/daemon/dist/channels/http.js +1 -1
- package/daemon/dist/channels/http.js.map +1 -1
- package/daemon/dist/core/config.js +1 -1
- package/daemon/dist/core/config.js.map +1 -1
- package/daemon/dist/core/control-commands.js +1 -1
- package/daemon/dist/core/cortex.js +4 -4
- package/daemon/dist/core/gateway-config.d.ts +1 -1
- package/daemon/dist/core/gateway-config.d.ts.map +1 -1
- package/daemon/dist/core/gateway-config.js +1 -1
- package/daemon/dist/core/gateway-config.js.map +1 -1
- package/daemon/dist/core/gateway.d.ts.map +1 -1
- package/daemon/dist/core/gateway.js +8 -34
- package/daemon/dist/core/gateway.js.map +1 -1
- package/daemon/dist/core/os-info.d.ts +65 -0
- package/daemon/dist/core/os-info.d.ts.map +1 -0
- package/daemon/dist/core/os-info.js +225 -0
- package/daemon/dist/core/os-info.js.map +1 -0
- package/daemon/dist/core/protocol-adapter.js +2 -2
- package/daemon/dist/core/thread-runtime-state.js +1 -1
- package/daemon/dist/http/context.js +2 -2
- package/daemon/dist/http/context.js.map +1 -1
- package/daemon/dist/http/router.d.ts.map +1 -1
- package/daemon/dist/http/router.js +4 -4
- package/daemon/dist/http/router.js.map +1 -1
- package/daemon/dist/http/routes/agents.d.ts.map +1 -1
- package/daemon/dist/http/routes/agents.js +13 -16
- package/daemon/dist/http/routes/agents.js.map +1 -1
- package/daemon/dist/http/routes/automations.d.ts.map +1 -1
- package/daemon/dist/http/routes/automations.js +27 -13
- package/daemon/dist/http/routes/automations.js.map +1 -1
- package/daemon/dist/http/routes/autonomy.js +4 -4
- package/daemon/dist/http/routes/autonomy.js.map +1 -1
- package/daemon/dist/http/routes/chat-input.d.ts +0 -1
- package/daemon/dist/http/routes/chat-input.d.ts.map +1 -1
- package/daemon/dist/http/routes/chat-input.js +0 -1
- package/daemon/dist/http/routes/chat-input.js.map +1 -1
- package/daemon/dist/http/routes/plugins.js +6 -6
- package/daemon/dist/http/routes/plugins.js.map +1 -1
- package/daemon/dist/http/routes/rpc.d.ts.map +1 -1
- package/daemon/dist/http/routes/rpc.js +43 -10
- package/daemon/dist/http/routes/rpc.js.map +1 -1
- package/daemon/dist/http/routes/sessions.js +15 -15
- package/daemon/dist/http/routes/sessions.js.map +1 -1
- package/daemon/dist/http/routes/skills.js +11 -11
- package/daemon/dist/http/routes/skills.js.map +1 -1
- package/daemon/dist/http/routes/system.d.ts +1 -0
- package/daemon/dist/http/routes/system.d.ts.map +1 -1
- package/daemon/dist/http/routes/system.js +316 -82
- package/daemon/dist/http/routes/system.js.map +1 -1
- package/daemon/dist/index.js +1 -1
- package/daemon/scripts/supen-daemon.js +1 -1
- package/dist/auth/login.js +1 -1
- package/dist/auth/login.js.map +1 -1
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +5 -4
- package/dist/bootstrap.js.map +1 -1
- package/dist/computer.js +14 -4
- package/dist/computer.js.map +1 -1
- package/dist/daemon-manage.js +9 -2
- package/dist/daemon-manage.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/os-identity.d.ts +6 -0
- package/dist/os-identity.js +68 -0
- package/dist/os-identity.js.map +1 -0
- package/package.json +2 -2
|
@@ -10,6 +10,7 @@ import { readSpaceEnvMap, updateSpaceEnvMap, withSupenLlmEnv } from '../../core/
|
|
|
10
10
|
import { readCodexSubscription } from '../../core/codex-subscription.js';
|
|
11
11
|
import { buildHubSnapshotForSpace } from '../../core/hub-snapshot.js';
|
|
12
12
|
import { readEnrollmentState, createEnrollmentToken, verifyEnrollmentToken, setTrustState } from '../../core/enrollment.js';
|
|
13
|
+
import { collectOsTelemetryFields } from '../../core/os-info.js';
|
|
13
14
|
import { getGatewayInstance, getLlmToken } from '../../core/gateway.js';
|
|
14
15
|
import { normalizeGatewayUplinkUrl, readGatewayConfig, writeGatewayConfig, } from '../../core/gateway-config.js';
|
|
15
16
|
import { ensureSession, getAllAgents, getDailyUsage, getGlobalUsage, getSessionsForAgent, getSessionUiEvents, updateSessionBackendDriverId, updateSessionSdkId, } from '../../core/store.js';
|
|
@@ -53,6 +54,33 @@ function readSqliteRows(dbPath, query) {
|
|
|
53
54
|
return null;
|
|
54
55
|
}
|
|
55
56
|
}
|
|
57
|
+
function quoteSqliteString(value) {
|
|
58
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
59
|
+
}
|
|
60
|
+
function runSqliteStatement(dbPath, query, params = []) {
|
|
61
|
+
try {
|
|
62
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
63
|
+
const db = new DatabaseSync(dbPath);
|
|
64
|
+
try {
|
|
65
|
+
db.prepare(query).run(...params);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
db.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
const expandedQuery = params.reduce((sql, param) => {
|
|
74
|
+
const value = typeof param === 'string' ? quoteSqliteString(param) : String(param);
|
|
75
|
+
return sql.replace('?', value);
|
|
76
|
+
}, query);
|
|
77
|
+
const result = spawnSync('sqlite3', [dbPath, expandedQuery], {
|
|
78
|
+
encoding: 'utf-8',
|
|
79
|
+
maxBuffer: 1024 * 1024,
|
|
80
|
+
});
|
|
81
|
+
return result.status === 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
56
84
|
function gatewayUplinkStatus(config = readGatewayConfig()) {
|
|
57
85
|
const gatewayUrl = (config.gateway_url || '').trim();
|
|
58
86
|
return {
|
|
@@ -218,7 +246,7 @@ function summarizeUiChunk(chunk) {
|
|
|
218
246
|
: 'Unknown error';
|
|
219
247
|
return { category: 'error', summary: truncateText(errorText) };
|
|
220
248
|
}
|
|
221
|
-
if (type === 'data-
|
|
249
|
+
if (type === 'data-codex-event') {
|
|
222
250
|
const data = chunk.data && typeof chunk.data === 'object'
|
|
223
251
|
? chunk.data
|
|
224
252
|
: {};
|
|
@@ -476,6 +504,15 @@ function readThreadIndexEntry(threadId) {
|
|
|
476
504
|
function readThreadStateEntry(threadId) {
|
|
477
505
|
return readThreadStateEntries(1000).find((entry) => entry.id === threadId) || null;
|
|
478
506
|
}
|
|
507
|
+
function archiveMirroredThread(threadId) {
|
|
508
|
+
const dbPath = path.join(localAgentHome(), 'state_5.sqlite');
|
|
509
|
+
if (!threadId || !fs.existsSync(dbPath))
|
|
510
|
+
return false;
|
|
511
|
+
const existing = readThreadStateEntry(threadId);
|
|
512
|
+
if (!existing)
|
|
513
|
+
return false;
|
|
514
|
+
return runSqliteStatement(dbPath, 'update threads set archived = 1 where id = ?', [threadId]);
|
|
515
|
+
}
|
|
479
516
|
function isoFromMillis(value) {
|
|
480
517
|
const ms = typeof value === 'number' ? value : Number(value);
|
|
481
518
|
if (!Number.isFinite(ms) || ms <= 0)
|
|
@@ -757,6 +794,66 @@ function isMirrorableCodexWorkspace(projectPath) {
|
|
|
757
794
|
}
|
|
758
795
|
return !isPathWithin(path.join(SUPEN_HOME, 'tasks'), projectPath);
|
|
759
796
|
}
|
|
797
|
+
function mimeTypeForWorkspaceFile(filePath) {
|
|
798
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
799
|
+
if (ext === '.md' || ext === '.markdown' || ext === '.mdx')
|
|
800
|
+
return 'text/markdown; charset=utf-8';
|
|
801
|
+
if (ext === '.txt' || ext === '.log' || ext === '.env')
|
|
802
|
+
return 'text/plain; charset=utf-8';
|
|
803
|
+
if (ext === '.json')
|
|
804
|
+
return 'application/json; charset=utf-8';
|
|
805
|
+
if (ext === '.html' || ext === '.htm')
|
|
806
|
+
return 'text/html; charset=utf-8';
|
|
807
|
+
if (ext === '.css')
|
|
808
|
+
return 'text/css; charset=utf-8';
|
|
809
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs')
|
|
810
|
+
return 'text/javascript; charset=utf-8';
|
|
811
|
+
if (ext === '.ts' || ext === '.tsx' || ext === '.jsx')
|
|
812
|
+
return 'text/plain; charset=utf-8';
|
|
813
|
+
if (ext === '.png')
|
|
814
|
+
return 'image/png';
|
|
815
|
+
if (ext === '.jpg' || ext === '.jpeg')
|
|
816
|
+
return 'image/jpeg';
|
|
817
|
+
if (ext === '.gif')
|
|
818
|
+
return 'image/gif';
|
|
819
|
+
if (ext === '.webp')
|
|
820
|
+
return 'image/webp';
|
|
821
|
+
if (ext === '.svg')
|
|
822
|
+
return 'image/svg+xml';
|
|
823
|
+
if (ext === '.pdf')
|
|
824
|
+
return 'application/pdf';
|
|
825
|
+
return 'application/octet-stream';
|
|
826
|
+
}
|
|
827
|
+
function serveRemoteFile(req, res, filePath) {
|
|
828
|
+
const stat = fs.statSync(filePath);
|
|
829
|
+
const basename = path.basename(filePath);
|
|
830
|
+
res.writeHead(200, {
|
|
831
|
+
'Content-Type': mimeTypeForWorkspaceFile(filePath),
|
|
832
|
+
'Content-Length': stat.size,
|
|
833
|
+
'Last-Modified': stat.mtime.toUTCString(),
|
|
834
|
+
'Cache-Control': 'no-store',
|
|
835
|
+
'Content-Disposition': `inline; filename="${basename.replace(/"/g, '')}"`,
|
|
836
|
+
});
|
|
837
|
+
if (req.method === 'HEAD') {
|
|
838
|
+
res.end();
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
fs.createReadStream(filePath).pipe(res);
|
|
842
|
+
}
|
|
843
|
+
function resolveRemoteFilePath(rawPath) {
|
|
844
|
+
const trimmed = rawPath.trim();
|
|
845
|
+
if (!trimmed)
|
|
846
|
+
return null;
|
|
847
|
+
const expanded = trimmed === '~'
|
|
848
|
+
? os.homedir()
|
|
849
|
+
: trimmed.startsWith('~/')
|
|
850
|
+
? path.join(os.homedir(), trimmed.slice(2))
|
|
851
|
+
: trimmed;
|
|
852
|
+
const resolved = path.resolve(expanded);
|
|
853
|
+
if (!path.isAbsolute(resolved))
|
|
854
|
+
return null;
|
|
855
|
+
return resolved;
|
|
856
|
+
}
|
|
760
857
|
function isAutomationRunThreadTitle(title) {
|
|
761
858
|
if (!title)
|
|
762
859
|
return false;
|
|
@@ -880,7 +977,9 @@ function sanitizeMirroredHistoryPayload(value, depth = 0) {
|
|
|
880
977
|
}
|
|
881
978
|
function safeMirroredHistoryAttachmentUrl(url) {
|
|
882
979
|
const trimmed = url.trim();
|
|
883
|
-
if (trimmed.startsWith('data:') &&
|
|
980
|
+
if (trimmed.startsWith('data:') &&
|
|
981
|
+
!trimmed.startsWith('data:image/') &&
|
|
982
|
+
trimmed.length > MIRRORED_THREAD_INLINE_DATA_URL_LIMIT) {
|
|
884
983
|
return '';
|
|
885
984
|
}
|
|
886
985
|
return trimmed;
|
|
@@ -902,12 +1001,15 @@ function imageAttachmentFromUrl(url, index) {
|
|
|
902
1001
|
const trimmed = url.trim();
|
|
903
1002
|
if (!trimmed)
|
|
904
1003
|
return null;
|
|
1004
|
+
const safeUrl = safeMirroredHistoryAttachmentUrl(trimmed);
|
|
1005
|
+
if (!safeUrl)
|
|
1006
|
+
return null;
|
|
905
1007
|
const mimeType = mimeTypeFromImageUrl(trimmed);
|
|
906
1008
|
const filename = `pasted-image-${index + 1}.${imageExtensionFromMimeType(mimeType)}`;
|
|
907
1009
|
return {
|
|
908
1010
|
name: filename,
|
|
909
1011
|
filename,
|
|
910
|
-
url:
|
|
1012
|
+
url: safeUrl,
|
|
911
1013
|
mimeType,
|
|
912
1014
|
mime_type: mimeType,
|
|
913
1015
|
};
|
|
@@ -1079,7 +1181,7 @@ function codexReplayEvent(eventId, timestamp, taskId, raw) {
|
|
|
1079
1181
|
timestamp,
|
|
1080
1182
|
task_id: taskId,
|
|
1081
1183
|
chunk: {
|
|
1082
|
-
type: 'data-
|
|
1184
|
+
type: 'data-codex-event',
|
|
1083
1185
|
id: eventId,
|
|
1084
1186
|
data: { raw },
|
|
1085
1187
|
},
|
|
@@ -1158,6 +1260,49 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1158
1260
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
1159
1261
|
});
|
|
1160
1262
|
};
|
|
1263
|
+
const pushWebUserMessage = (event) => {
|
|
1264
|
+
if (event.source !== 'web' || event.event_type !== 'user_message')
|
|
1265
|
+
return;
|
|
1266
|
+
const payload = event.raw_payload && typeof event.raw_payload === 'object'
|
|
1267
|
+
? event.raw_payload
|
|
1268
|
+
: {};
|
|
1269
|
+
const text = typeof payload.content === 'string' ? payload.content : '';
|
|
1270
|
+
const attachments = Array.isArray(payload.attachments)
|
|
1271
|
+
? payload.attachments
|
|
1272
|
+
.map((attachment, index) => {
|
|
1273
|
+
if (!attachment || typeof attachment !== 'object')
|
|
1274
|
+
return null;
|
|
1275
|
+
const record = attachment;
|
|
1276
|
+
const url = typeof record.url === 'string' ? record.url :
|
|
1277
|
+
typeof record.supenUrl === 'string' ? record.supenUrl :
|
|
1278
|
+
typeof record.path === 'string' ? record.path :
|
|
1279
|
+
typeof record.taskPath === 'string' ? record.taskPath :
|
|
1280
|
+
'';
|
|
1281
|
+
if (!url.trim())
|
|
1282
|
+
return null;
|
|
1283
|
+
const filename = typeof record.filename === 'string' ? record.filename :
|
|
1284
|
+
typeof record.name === 'string' ? record.name :
|
|
1285
|
+
`attachment-${index + 1}`;
|
|
1286
|
+
const mimeType = typeof record.mimeType === 'string' ? record.mimeType :
|
|
1287
|
+
typeof record.mime_type === 'string' ? record.mime_type :
|
|
1288
|
+
'application/octet-stream';
|
|
1289
|
+
const safeUrl = safeMirroredHistoryAttachmentUrl(url);
|
|
1290
|
+
if (!safeUrl)
|
|
1291
|
+
return null;
|
|
1292
|
+
return {
|
|
1293
|
+
name: filename,
|
|
1294
|
+
filename,
|
|
1295
|
+
url: safeUrl,
|
|
1296
|
+
mimeType,
|
|
1297
|
+
mime_type: mimeType,
|
|
1298
|
+
};
|
|
1299
|
+
})
|
|
1300
|
+
.filter((attachment) => Boolean(attachment))
|
|
1301
|
+
: [];
|
|
1302
|
+
const timestamp = typeof payload.timestamp === 'string' ? payload.timestamp : event.received_at;
|
|
1303
|
+
const messageId = typeof payload.id === 'string' ? payload.id : `${threadId}-web-user-${event.sequence}`;
|
|
1304
|
+
pushUserMessage(text, timestamp, messageId, attachments);
|
|
1305
|
+
};
|
|
1161
1306
|
const pushEvent = (timestamp, chunk) => {
|
|
1162
1307
|
eventIndex += 1;
|
|
1163
1308
|
events.push({
|
|
@@ -1368,15 +1513,20 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1368
1513
|
task_id: threadId,
|
|
1369
1514
|
});
|
|
1370
1515
|
}
|
|
1371
|
-
|
|
1516
|
+
const eventLogEvents = listRecentThreadEventsAfter(eventLogThreadId, 0, {
|
|
1372
1517
|
limit: MIRRORED_THREAD_STREAM_REPLAY_LIMIT,
|
|
1373
1518
|
maxBytes: MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES,
|
|
1374
|
-
})
|
|
1519
|
+
});
|
|
1520
|
+
for (const event of eventLogEvents) {
|
|
1521
|
+
pushWebUserMessage(event);
|
|
1522
|
+
}
|
|
1523
|
+
for (const event of eventLogEvents) {
|
|
1375
1524
|
const chunk = threadStreamChunkForEvent(event);
|
|
1376
1525
|
if (!chunk)
|
|
1377
1526
|
continue;
|
|
1378
1527
|
events.push({
|
|
1379
1528
|
id: `${threadId}-event-log-${event.sequence}`,
|
|
1529
|
+
sequence: event.sequence,
|
|
1380
1530
|
timestamp: event.received_at,
|
|
1381
1531
|
task_id: threadId,
|
|
1382
1532
|
chunk,
|
|
@@ -1539,11 +1689,13 @@ function threadStreamChunkForEvent(event) {
|
|
|
1539
1689
|
return null;
|
|
1540
1690
|
if (event.raw_payload &&
|
|
1541
1691
|
typeof event.raw_payload === 'object' &&
|
|
1542
|
-
!Array.isArray(event.raw_payload)
|
|
1692
|
+
!Array.isArray(event.raw_payload) &&
|
|
1693
|
+
typeof event.raw_payload.type === 'string' &&
|
|
1694
|
+
String(event.raw_payload.type).trim()) {
|
|
1543
1695
|
return event.raw_payload;
|
|
1544
1696
|
}
|
|
1545
1697
|
return {
|
|
1546
|
-
type: 'data-
|
|
1698
|
+
type: 'data-codex-event',
|
|
1547
1699
|
data: {
|
|
1548
1700
|
eventType: event.event_type,
|
|
1549
1701
|
raw: event.raw_payload,
|
|
@@ -1653,19 +1805,87 @@ function codexConnectSnapshot() {
|
|
|
1653
1805
|
error: codexConnectRuntime.error,
|
|
1654
1806
|
};
|
|
1655
1807
|
}
|
|
1808
|
+
function detectGlobalNpmRoot() {
|
|
1809
|
+
const result = spawnSync('npm', ['root', '-g'], {
|
|
1810
|
+
encoding: 'utf8',
|
|
1811
|
+
timeout: 3000,
|
|
1812
|
+
});
|
|
1813
|
+
if (result.status !== 0)
|
|
1814
|
+
return null;
|
|
1815
|
+
const root = String(result.stdout || '').trim();
|
|
1816
|
+
if (!root)
|
|
1817
|
+
return null;
|
|
1818
|
+
try {
|
|
1819
|
+
return fs.realpathSync(root);
|
|
1820
|
+
}
|
|
1821
|
+
catch {
|
|
1822
|
+
return root;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
function inferCliInstallSource(name, executablePath, resolvedPath) {
|
|
1826
|
+
if (!executablePath && !resolvedPath)
|
|
1827
|
+
return { install_source: null, update_supported: false };
|
|
1828
|
+
const candidates = [executablePath || '', resolvedPath || ''].map((value) => value.toLowerCase());
|
|
1829
|
+
const normalized = candidates.join('\n');
|
|
1830
|
+
if (normalized.includes('/.local/share/pnpm/') || normalized.includes('/pnpm/')) {
|
|
1831
|
+
return { install_source: 'pnpm', update_supported: false };
|
|
1832
|
+
}
|
|
1833
|
+
if (normalized.includes('/homebrew/') || normalized.includes('/opt/homebrew/') || normalized.includes('/cellar/')) {
|
|
1834
|
+
return { install_source: 'homebrew', update_supported: false };
|
|
1835
|
+
}
|
|
1836
|
+
if (name === 'codex') {
|
|
1837
|
+
const npmRoot = detectGlobalNpmRoot();
|
|
1838
|
+
const codexPackagePath = resolvedPath || executablePath || '';
|
|
1839
|
+
if (npmRoot && codexPackagePath.startsWith(`${npmRoot}${path.sep}@openai${path.sep}codex${path.sep}`)) {
|
|
1840
|
+
return { install_source: 'npm-global', update_supported: true };
|
|
1841
|
+
}
|
|
1842
|
+
if (normalized.includes('/node_modules/@openai/codex/')) {
|
|
1843
|
+
return { install_source: 'node-package', update_supported: false };
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
if (name === 'gemini') {
|
|
1847
|
+
const npmRoot = detectGlobalNpmRoot();
|
|
1848
|
+
const geminiPackagePath = resolvedPath || executablePath || '';
|
|
1849
|
+
if (npmRoot && geminiPackagePath.startsWith(`${npmRoot}${path.sep}@google${path.sep}gemini-cli${path.sep}`)) {
|
|
1850
|
+
return { install_source: 'npm-global', update_supported: true };
|
|
1851
|
+
}
|
|
1852
|
+
if (normalized.includes('/node_modules/@google/gemini-cli/')) {
|
|
1853
|
+
return { install_source: 'node-package', update_supported: false };
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return { install_source: null, update_supported: false };
|
|
1857
|
+
}
|
|
1656
1858
|
function detectCliInstalled(name) {
|
|
1657
|
-
const cmd = spawnSync('sh', ['-
|
|
1859
|
+
const cmd = spawnSync('sh', ['-c', `command -v ${name}`], {
|
|
1658
1860
|
encoding: 'utf8',
|
|
1659
1861
|
timeout: 3000,
|
|
1660
1862
|
});
|
|
1661
|
-
if (cmd.status !== 0)
|
|
1662
|
-
return { installed: false, version: null };
|
|
1863
|
+
if (cmd.status !== 0) {
|
|
1864
|
+
return { installed: false, version: null, path: null, resolved_path: null, install_source: null, update_supported: false };
|
|
1865
|
+
}
|
|
1866
|
+
const executablePath = String(cmd.stdout || '').split(/\r?\n/g).find(Boolean)?.trim() || null;
|
|
1867
|
+
let resolvedPath = null;
|
|
1868
|
+
if (executablePath) {
|
|
1869
|
+
try {
|
|
1870
|
+
resolvedPath = fs.realpathSync(executablePath);
|
|
1871
|
+
}
|
|
1872
|
+
catch {
|
|
1873
|
+
resolvedPath = executablePath;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1663
1876
|
const versionCmd = spawnSync(name, ['--version'], {
|
|
1664
1877
|
encoding: 'utf8',
|
|
1665
1878
|
timeout: 5000,
|
|
1666
1879
|
});
|
|
1667
1880
|
const output = trimLogNoise(`${versionCmd.stdout || ''}\n${versionCmd.stderr || ''}`) || '';
|
|
1668
|
-
|
|
1881
|
+
const source = inferCliInstallSource(name, executablePath, resolvedPath);
|
|
1882
|
+
return {
|
|
1883
|
+
installed: true,
|
|
1884
|
+
version: output || null,
|
|
1885
|
+
path: executablePath,
|
|
1886
|
+
resolved_path: resolvedPath,
|
|
1887
|
+
...source,
|
|
1888
|
+
};
|
|
1669
1889
|
}
|
|
1670
1890
|
function detectCodexAuthStatus(installed) {
|
|
1671
1891
|
if (!installed)
|
|
@@ -1799,6 +2019,10 @@ function readCodingCliStatusPayload() {
|
|
|
1799
2019
|
name,
|
|
1800
2020
|
installed: detected.installed,
|
|
1801
2021
|
version: detected.version,
|
|
2022
|
+
path: detected.path,
|
|
2023
|
+
resolved_path: detected.resolved_path,
|
|
2024
|
+
install_source: detected.install_source,
|
|
2025
|
+
update_supported: detected.update_supported,
|
|
1802
2026
|
};
|
|
1803
2027
|
});
|
|
1804
2028
|
const codexInstalled = clis.find((cli) => cli.name === 'codex')?.installed ?? false;
|
|
@@ -1819,6 +2043,10 @@ function readCodingCliStatusPayload() {
|
|
|
1819
2043
|
installed: detected.installed,
|
|
1820
2044
|
version: detected.version,
|
|
1821
2045
|
managed: app.managed,
|
|
2046
|
+
path: detected.path,
|
|
2047
|
+
resolved_path: detected.resolved_path,
|
|
2048
|
+
install_source: detected.install_source,
|
|
2049
|
+
update_supported: detected.update_supported,
|
|
1822
2050
|
};
|
|
1823
2051
|
});
|
|
1824
2052
|
return {
|
|
@@ -2017,15 +2245,15 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2017
2245
|
writeJson(res, 200, { status: 'ok' });
|
|
2018
2246
|
return true;
|
|
2019
2247
|
}
|
|
2020
|
-
if (pathname === '/api/
|
|
2248
|
+
if (pathname === '/api/computers/{computer_id}/openapi.json' && method === 'GET') {
|
|
2021
2249
|
writeJson(res, 200, buildDaemonOpenApiSpec());
|
|
2022
2250
|
return true;
|
|
2023
2251
|
}
|
|
2024
|
-
if (pathname === '/api/
|
|
2252
|
+
if (pathname === '/api/computers/{computer_id}/models' && method === 'GET') {
|
|
2025
2253
|
writeJson(res, 200, { models: MODELS_REGISTRY });
|
|
2026
2254
|
return true;
|
|
2027
2255
|
}
|
|
2028
|
-
if (pathname === '/api/
|
|
2256
|
+
if (pathname === '/api/computers/{computer_id}/config/reload' && method === 'POST') {
|
|
2029
2257
|
const before = {
|
|
2030
2258
|
total_models: MODELS_REGISTRY.length,
|
|
2031
2259
|
default_model: DEFAULT_MODEL?.id ?? null,
|
|
@@ -2041,11 +2269,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2041
2269
|
});
|
|
2042
2270
|
return true;
|
|
2043
2271
|
}
|
|
2044
|
-
if (pathname === '/api/
|
|
2272
|
+
if (pathname === '/api/computers/{computer_id}/config-summary' && method === 'GET') {
|
|
2045
2273
|
writeJson(res, 200, readConfigSummary());
|
|
2046
2274
|
return true;
|
|
2047
2275
|
}
|
|
2048
|
-
if (pathname === '/api/
|
|
2276
|
+
if (pathname === '/api/computers/{computer_id}/logs' && method === 'GET') {
|
|
2049
2277
|
const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
|
|
2050
2278
|
const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
|
|
2051
2279
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
@@ -2057,7 +2285,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2057
2285
|
}));
|
|
2058
2286
|
return true;
|
|
2059
2287
|
}
|
|
2060
|
-
if (pathname === '/api/
|
|
2288
|
+
if (pathname === '/api/computers/{computer_id}/logs/stream' && method === 'GET') {
|
|
2061
2289
|
const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
|
|
2062
2290
|
const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
|
|
2063
2291
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
@@ -2100,7 +2328,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2100
2328
|
writeSse(res);
|
|
2101
2329
|
return true;
|
|
2102
2330
|
}
|
|
2103
|
-
if (pathname === '/api/
|
|
2331
|
+
if (pathname === '/api/computers/{computer_id}/config-yaml' && method === 'GET') {
|
|
2104
2332
|
try {
|
|
2105
2333
|
const yaml = readConfigYamlFile();
|
|
2106
2334
|
writeJson(res, 200, { yaml });
|
|
@@ -2110,7 +2338,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2110
2338
|
}
|
|
2111
2339
|
return true;
|
|
2112
2340
|
}
|
|
2113
|
-
if (pathname === '/api/
|
|
2341
|
+
if (pathname === '/api/computers/{computer_id}/config-yaml' && method === 'PUT') {
|
|
2114
2342
|
try {
|
|
2115
2343
|
const parsed = await readJsonBody(req);
|
|
2116
2344
|
const yamlText = typeof parsed === 'object' && parsed && typeof parsed.yaml === 'string'
|
|
@@ -2137,11 +2365,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2137
2365
|
}
|
|
2138
2366
|
return true;
|
|
2139
2367
|
}
|
|
2140
|
-
if (pathname === '/api/
|
|
2368
|
+
if (pathname === '/api/computers/{computer_id}/gateway-uplink' && method === 'GET') {
|
|
2141
2369
|
writeJson(res, 200, gatewayUplinkStatus());
|
|
2142
2370
|
return true;
|
|
2143
2371
|
}
|
|
2144
|
-
if (pathname === '/api/
|
|
2372
|
+
if (pathname === '/api/computers/{computer_id}/gateway-uplink' && (method === 'PUT' || method === 'PATCH')) {
|
|
2145
2373
|
if (process.env.SUPEN_GATEWAY_URL) {
|
|
2146
2374
|
writeProtocolError(res, 409, 'config_error', 'env_override', 'SUPEN_GATEWAY_URL is set; update the daemon environment to change the gateway uplink.');
|
|
2147
2375
|
return true;
|
|
@@ -2184,11 +2412,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2184
2412
|
}
|
|
2185
2413
|
return true;
|
|
2186
2414
|
}
|
|
2187
|
-
if (pathname === '/api/
|
|
2415
|
+
if (pathname === '/api/computers/{computer_id}/env' && method === 'GET') {
|
|
2188
2416
|
writeJson(res, 200, { env: readSpaceEnvMap() });
|
|
2189
2417
|
return true;
|
|
2190
2418
|
}
|
|
2191
|
-
if (pathname === '/api/
|
|
2419
|
+
if (pathname === '/api/computers/{computer_id}/llm-env' && method === 'GET') {
|
|
2192
2420
|
const token = getLlmToken() || process.env.SUPEN_LLM_TOKEN || process.env.SUPEN_LLM_API_KEY || '';
|
|
2193
2421
|
const env = withSupenLlmEnv({
|
|
2194
2422
|
...process.env,
|
|
@@ -2208,15 +2436,15 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2208
2436
|
});
|
|
2209
2437
|
return true;
|
|
2210
2438
|
}
|
|
2211
|
-
if (pathname === '/api/
|
|
2439
|
+
if (pathname === '/api/computers/{computer_id}/apps' && method === 'GET') {
|
|
2212
2440
|
writeJson(res, 200, readCodingCliStatusPayload());
|
|
2213
2441
|
return true;
|
|
2214
2442
|
}
|
|
2215
|
-
if (pathname === '/api/
|
|
2443
|
+
if (pathname === '/api/computers/{computer_id}/runtime-models' && method === 'GET') {
|
|
2216
2444
|
writeJson(res, 200, readRuntimeModelStatusPayload());
|
|
2217
2445
|
return true;
|
|
2218
2446
|
}
|
|
2219
|
-
if (pathname === '/api/
|
|
2447
|
+
if (pathname === '/api/computers/{computer_id}/runtime-models/default' && method === 'PUT') {
|
|
2220
2448
|
try {
|
|
2221
2449
|
const parsed = await readJsonBody(req);
|
|
2222
2450
|
const model = typeof parsed === 'object' && parsed && typeof parsed.model === 'string'
|
|
@@ -2237,7 +2465,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2237
2465
|
}
|
|
2238
2466
|
return true;
|
|
2239
2467
|
}
|
|
2240
|
-
const spaceCodexTransportDefaultMatch = pathname.match(/^\/api\/
|
|
2468
|
+
const spaceCodexTransportDefaultMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/codex\/transports\/([^/]+)\/default$/);
|
|
2241
2469
|
if (spaceCodexTransportDefaultMatch && method === 'PUT') {
|
|
2242
2470
|
try {
|
|
2243
2471
|
const transport = normalizeCodexTransport(decodeURIComponent(spaceCodexTransportDefaultMatch[1] || '').trim());
|
|
@@ -2257,7 +2485,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2257
2485
|
}
|
|
2258
2486
|
return true;
|
|
2259
2487
|
}
|
|
2260
|
-
const spaceAppDefaultMatch = pathname.match(/^\/api\/
|
|
2488
|
+
const spaceAppDefaultMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/default$/);
|
|
2261
2489
|
if (spaceAppDefaultMatch && method === 'PUT') {
|
|
2262
2490
|
try {
|
|
2263
2491
|
const cli = normalizeCliName(decodeURIComponent(spaceAppDefaultMatch[1] || '').trim());
|
|
@@ -2277,7 +2505,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2277
2505
|
}
|
|
2278
2506
|
return true;
|
|
2279
2507
|
}
|
|
2280
|
-
const spaceAppInstallMatch = pathname.match(/^\/api\/
|
|
2508
|
+
const spaceAppInstallMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/install$/);
|
|
2281
2509
|
if (spaceAppInstallMatch && method === 'POST') {
|
|
2282
2510
|
try {
|
|
2283
2511
|
const cli = normalizeCliName(decodeURIComponent(spaceAppInstallMatch[1] || '').trim());
|
|
@@ -2289,6 +2517,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2289
2517
|
writeProtocolError(res, 400, 'validation_error', 'install_not_supported', 'Only codex and gemini support managed install at the moment');
|
|
2290
2518
|
return true;
|
|
2291
2519
|
}
|
|
2520
|
+
const current = detectCliInstalled(cli);
|
|
2521
|
+
if (current.installed && !current.update_supported) {
|
|
2522
|
+
writeProtocolError(res, 400, 'validation_error', 'coding_cli_update_not_supported', `${cli} is installed via ${current.install_source || current.resolved_path || current.path || 'an unknown source'}; update it with that installer on this computer.`);
|
|
2523
|
+
return true;
|
|
2524
|
+
}
|
|
2292
2525
|
const installSpec = MANAGED_CODING_CLI_INSTALL_COMMANDS[cli];
|
|
2293
2526
|
const result = spawnSync(installSpec.command, installSpec.args, {
|
|
2294
2527
|
encoding: 'utf8',
|
|
@@ -2310,7 +2543,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2310
2543
|
}
|
|
2311
2544
|
return true;
|
|
2312
2545
|
}
|
|
2313
|
-
const spaceAppConnectMatch = pathname.match(/^\/api\/
|
|
2546
|
+
const spaceAppConnectMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/connect$/);
|
|
2314
2547
|
if (spaceAppConnectMatch && method === 'POST') {
|
|
2315
2548
|
const cli = normalizeCliName(decodeURIComponent(spaceAppConnectMatch[1] || '').trim());
|
|
2316
2549
|
if (cli !== 'codex') {
|
|
@@ -2343,7 +2576,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2343
2576
|
});
|
|
2344
2577
|
return true;
|
|
2345
2578
|
}
|
|
2346
|
-
const spaceAppSessionMatch = pathname.match(/^\/api\/
|
|
2579
|
+
const spaceAppSessionMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/session$/);
|
|
2347
2580
|
if (spaceAppSessionMatch && method === 'DELETE') {
|
|
2348
2581
|
const cli = normalizeCliName(decodeURIComponent(spaceAppSessionMatch[1] || '').trim());
|
|
2349
2582
|
if (cli !== 'codex') {
|
|
@@ -2362,7 +2595,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2362
2595
|
writeJson(res, 200, { ok: true, status: readCodingCliStatusPayload() });
|
|
2363
2596
|
return true;
|
|
2364
2597
|
}
|
|
2365
|
-
if (pathname === '/api/
|
|
2598
|
+
if (pathname === '/api/computers/{computer_id}/env' && method === 'PUT') {
|
|
2366
2599
|
try {
|
|
2367
2600
|
const parsed = await readJsonBody(req);
|
|
2368
2601
|
const updates = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)
|
|
@@ -2380,20 +2613,20 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2380
2613
|
}
|
|
2381
2614
|
return true;
|
|
2382
2615
|
}
|
|
2383
|
-
if (pathname === '/api/
|
|
2616
|
+
if (pathname === '/api/computers/{computer_id}/hub-snapshot' && method === 'GET') {
|
|
2384
2617
|
const spaceId = (url.searchParams.get('space_id') || process.env.SUPEN_SPACE_ID || 'local').trim() || 'local';
|
|
2385
2618
|
writeJson(res, 200, buildHubSnapshotForSpace(spaceId));
|
|
2386
2619
|
return true;
|
|
2387
2620
|
}
|
|
2388
|
-
if (pathname === '/api/
|
|
2621
|
+
if (pathname === '/api/computers/{computer_id}/usage' && method === 'GET') {
|
|
2389
2622
|
writeJson(res, 200, getGlobalUsage());
|
|
2390
2623
|
return true;
|
|
2391
2624
|
}
|
|
2392
|
-
if (pathname === '/api/
|
|
2625
|
+
if (pathname === '/api/computers/{computer_id}/usage/daily' && method === 'GET') {
|
|
2393
2626
|
writeJson(res, 200, { daily: getDailyUsage() });
|
|
2394
2627
|
return true;
|
|
2395
2628
|
}
|
|
2396
|
-
if (pathname === '/api/
|
|
2629
|
+
if (pathname === '/api/computers/{computer_id}/codex/subscription' && method === 'GET') {
|
|
2397
2630
|
try {
|
|
2398
2631
|
writeJson(res, 200, await readCodexSubscription());
|
|
2399
2632
|
}
|
|
@@ -2402,17 +2635,45 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2402
2635
|
}
|
|
2403
2636
|
return true;
|
|
2404
2637
|
}
|
|
2405
|
-
if (pathname === '/api/
|
|
2638
|
+
if (pathname === '/api/computers/{computer_id}/quota-status' && method === 'GET') {
|
|
2406
2639
|
writeJson(res, 200, readLatestSpaceQuotaStatus());
|
|
2407
2640
|
return true;
|
|
2408
2641
|
}
|
|
2409
|
-
if (pathname === '/api/
|
|
2642
|
+
if (pathname === '/api/computers/{computer_id}/codex/threads' && method === 'GET') {
|
|
2410
2643
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
2411
2644
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : MIRRORED_THREAD_LIMIT;
|
|
2412
2645
|
writeJson(res, 200, readMirroredTaskProjects(Number.isFinite(limit) ? limit : MIRRORED_THREAD_LIMIT));
|
|
2413
2646
|
return true;
|
|
2414
2647
|
}
|
|
2415
|
-
|
|
2648
|
+
if (pathname === '/api/computers/{computer_id}/files/preview' && (method === 'GET' || method === 'HEAD')) {
|
|
2649
|
+
const requestedPath = coerceSingleQueryParam(url.searchParams.get('path')) || '';
|
|
2650
|
+
const filePath = resolveRemoteFilePath(requestedPath);
|
|
2651
|
+
if (!filePath) {
|
|
2652
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_file_path', 'A valid file path is required.');
|
|
2653
|
+
return true;
|
|
2654
|
+
}
|
|
2655
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
2656
|
+
writeProtocolError(res, 404, 'not_found', 'remote_file_not_found', 'File not found.');
|
|
2657
|
+
return true;
|
|
2658
|
+
}
|
|
2659
|
+
serveRemoteFile(req, res, filePath);
|
|
2660
|
+
return true;
|
|
2661
|
+
}
|
|
2662
|
+
const mirroredThreadArchiveMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/archive$/);
|
|
2663
|
+
if (mirroredThreadArchiveMatch && method === 'POST') {
|
|
2664
|
+
const threadId = decodeURIComponent(mirroredThreadArchiveMatch[1] || '').trim();
|
|
2665
|
+
if (!threadId) {
|
|
2666
|
+
writeProtocolError(res, 400, 'validation_error', 'missing_thread_id', 'Thread id is required.');
|
|
2667
|
+
return true;
|
|
2668
|
+
}
|
|
2669
|
+
if (!archiveMirroredThread(threadId)) {
|
|
2670
|
+
writeProtocolError(res, 404, 'not_found', 'mirrored_thread_not_found', 'Mirrored task was not found.');
|
|
2671
|
+
return true;
|
|
2672
|
+
}
|
|
2673
|
+
writeJson(res, 200, { ok: true, archived: true });
|
|
2674
|
+
return true;
|
|
2675
|
+
}
|
|
2676
|
+
const mirroredThreadStreamMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/stream$/);
|
|
2416
2677
|
if (mirroredThreadStreamMatch && method === 'GET') {
|
|
2417
2678
|
const threadId = decodeURIComponent(mirroredThreadStreamMatch[1] || '').trim();
|
|
2418
2679
|
if (!threadId) {
|
|
@@ -2455,7 +2716,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2455
2716
|
res.flush?.();
|
|
2456
2717
|
return true;
|
|
2457
2718
|
}
|
|
2458
|
-
const mirroredThreadHistoryMatch = pathname.match(/^\/api\/
|
|
2719
|
+
const mirroredThreadHistoryMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/messages$/);
|
|
2459
2720
|
if (mirroredThreadHistoryMatch && method === 'GET') {
|
|
2460
2721
|
const threadId = decodeURIComponent(mirroredThreadHistoryMatch[1] || '').trim();
|
|
2461
2722
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
@@ -2470,7 +2731,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2470
2731
|
writeJson(res, 200, history);
|
|
2471
2732
|
return true;
|
|
2472
2733
|
}
|
|
2473
|
-
const mirroredThreadAdoptMatch = pathname.match(/^\/api\/
|
|
2734
|
+
const mirroredThreadAdoptMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/adopt$/);
|
|
2474
2735
|
if (mirroredThreadAdoptMatch && method === 'POST') {
|
|
2475
2736
|
const threadId = decodeURIComponent(mirroredThreadAdoptMatch[1] || '').trim();
|
|
2476
2737
|
const parsed = await readJsonBody(req);
|
|
@@ -2487,7 +2748,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2487
2748
|
writeJson(res, 200, { thread: serializeAdoptedMirroredThread(session) });
|
|
2488
2749
|
return true;
|
|
2489
2750
|
}
|
|
2490
|
-
if (pathname === '/api/
|
|
2751
|
+
if (pathname === '/api/computers/{computer_id}/codex/projects/open' && method === 'POST') {
|
|
2491
2752
|
try {
|
|
2492
2753
|
const parsed = await readJsonBody(req);
|
|
2493
2754
|
const projectPath = parsed && typeof parsed === 'object' && typeof parsed.path === 'string'
|
|
@@ -2514,12 +2775,12 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2514
2775
|
}
|
|
2515
2776
|
return true;
|
|
2516
2777
|
}
|
|
2517
|
-
if (pathname === '/api/
|
|
2778
|
+
if (pathname === '/api/computers/{computer_id}/mcp/servers' && method === 'GET') {
|
|
2518
2779
|
writeJson(res, 200, { servers: buildMcpSettingsResponse().servers });
|
|
2519
2780
|
return true;
|
|
2520
2781
|
}
|
|
2521
2782
|
/** Tools discovered via MCP `tools/list` for each connected server (in-memory; empty if servers not connected). */
|
|
2522
|
-
if (pathname === '/api/
|
|
2783
|
+
if (pathname === '/api/computers/{computer_id}/mcp/tool-catalog' && method === 'GET') {
|
|
2523
2784
|
try {
|
|
2524
2785
|
const force = url.searchParams.get('refresh') === '1' || url.searchParams.get('force') === '1';
|
|
2525
2786
|
const mgr = await getMcpManager({ forceConfigRefresh: force });
|
|
@@ -2538,11 +2799,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2538
2799
|
}
|
|
2539
2800
|
return true;
|
|
2540
2801
|
}
|
|
2541
|
-
if (pathname === '/api/
|
|
2802
|
+
if (pathname === '/api/computers/{computer_id}/mcp/settings' && method === 'GET') {
|
|
2542
2803
|
writeJson(res, 200, buildMcpSettingsResponse());
|
|
2543
2804
|
return true;
|
|
2544
2805
|
}
|
|
2545
|
-
if (pathname === '/api/
|
|
2806
|
+
if (pathname === '/api/computers/{computer_id}/mcp/settings' && method === 'PUT') {
|
|
2546
2807
|
const parsed = await readJsonBody(req);
|
|
2547
2808
|
const allowedKeys = new Set(listMcpEnvKeys());
|
|
2548
2809
|
const payloadEnv = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object'
|
|
@@ -2563,21 +2824,8 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2563
2824
|
writeJson(res, 200, buildMcpSettingsResponse());
|
|
2564
2825
|
return true;
|
|
2565
2826
|
}
|
|
2566
|
-
if (pathname === '/api/
|
|
2567
|
-
const cpus = os.cpus();
|
|
2568
|
-
const memTotal = os.totalmem();
|
|
2569
|
-
const memFree = os.freemem();
|
|
2827
|
+
if (pathname === '/api/computers/{computer_id}' && method === 'GET') {
|
|
2570
2828
|
const enroll = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
2571
|
-
let disk_total;
|
|
2572
|
-
let disk_free;
|
|
2573
|
-
let disk_used;
|
|
2574
|
-
try {
|
|
2575
|
-
const stats = fs.statfsSync(SUPEN_HOME);
|
|
2576
|
-
disk_total = stats.bsize * stats.blocks;
|
|
2577
|
-
disk_free = stats.bsize * stats.bavail;
|
|
2578
|
-
disk_used = disk_total - disk_free;
|
|
2579
|
-
}
|
|
2580
|
-
catch { /* ignore */ }
|
|
2581
2829
|
writeJson(res, 200, {
|
|
2582
2830
|
daemon_id: enroll.daemon_id,
|
|
2583
2831
|
hostname: os.hostname(),
|
|
@@ -2602,31 +2850,17 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2602
2850
|
queued_tasks: 0,
|
|
2603
2851
|
total_slots: 0,
|
|
2604
2852
|
},
|
|
2605
|
-
os:
|
|
2606
|
-
hostname: os.hostname(),
|
|
2607
|
-
platform: os.platform(),
|
|
2608
|
-
arch: os.arch(),
|
|
2609
|
-
cpus: cpus.length,
|
|
2610
|
-
cpu_model: cpus[0]?.model || 'Unknown',
|
|
2611
|
-
load_avg: os.loadavg(),
|
|
2612
|
-
mem_total: memTotal,
|
|
2613
|
-
mem_free: memFree,
|
|
2614
|
-
mem_used: memTotal - memFree,
|
|
2615
|
-
uptime: os.uptime(),
|
|
2616
|
-
disk_total,
|
|
2617
|
-
disk_free,
|
|
2618
|
-
disk_used,
|
|
2619
|
-
},
|
|
2853
|
+
os: collectOsTelemetryFields(),
|
|
2620
2854
|
});
|
|
2621
2855
|
return true;
|
|
2622
2856
|
}
|
|
2623
2857
|
// ── Enrollment ──
|
|
2624
|
-
if (pathname === '/api/
|
|
2858
|
+
if (pathname === '/api/computers/{computer_id}/enroll/status' && method === 'GET') {
|
|
2625
2859
|
const state = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
2626
2860
|
writeJson(res, 200, state);
|
|
2627
2861
|
return true;
|
|
2628
2862
|
}
|
|
2629
|
-
if (pathname === '/api/
|
|
2863
|
+
if (pathname === '/api/computers/{computer_id}/enroll/verify' && method === 'POST') {
|
|
2630
2864
|
const parsed = await readJsonBody(req);
|
|
2631
2865
|
const result = verifyEnrollmentToken({
|
|
2632
2866
|
token: parsed.token,
|
|
@@ -2636,12 +2870,12 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2636
2870
|
writeJson(res, result.ok ? 200 : 401, result);
|
|
2637
2871
|
return true;
|
|
2638
2872
|
}
|
|
2639
|
-
if (pathname === '/api/
|
|
2640
|
-
writeProtocolError(res, 410, 'auth_error', 'trust_endpoint_removed', 'Direct trust elevation is disabled. Use the enrollment flow (/api/
|
|
2873
|
+
if (pathname === '/api/computers/{computer_id}/trust' && method === 'POST') {
|
|
2874
|
+
writeProtocolError(res, 410, 'auth_error', 'trust_endpoint_removed', 'Direct trust elevation is disabled. Use the enrollment flow (/api/computers/{computer_id}/enroll/token + /api/computers/{computer_id}/enroll/verify) to transition a daemon to trusted state.');
|
|
2641
2875
|
return true;
|
|
2642
2876
|
}
|
|
2643
2877
|
// Create enrollment token
|
|
2644
|
-
if (pathname === '/api/
|
|
2878
|
+
if (pathname === '/api/computers/{computer_id}/enroll/token' && method === 'POST') {
|
|
2645
2879
|
try {
|
|
2646
2880
|
const parsed = await readJsonBody(req);
|
|
2647
2881
|
const ttlMinutes = parsed?.ttl_minutes || DEFAULT_TOKEN_TTL_MINUTES;
|
|
@@ -2661,7 +2895,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2661
2895
|
return true;
|
|
2662
2896
|
}
|
|
2663
2897
|
// Revoke enrollment
|
|
2664
|
-
if (pathname === '/api/
|
|
2898
|
+
if (pathname === '/api/computers/{computer_id}/enroll/revoke' && method === 'POST') {
|
|
2665
2899
|
try {
|
|
2666
2900
|
setTrustState('revoked', { daemonId: DAEMON_HOSTNAME || undefined });
|
|
2667
2901
|
writeJson(res, 200, { ok: true, trust_state: 'revoked' });
|