@supen-ai/cli 0.1.10 → 0.1.12
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 +15 -7
- 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/coding-cli-status-cache.d.ts +19 -0
- package/daemon/dist/core/coding-cli-status-cache.d.ts.map +1 -0
- package/daemon/dist/core/coding-cli-status-cache.js +122 -0
- package/daemon/dist/core/coding-cli-status-cache.js.map +1 -0
- 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 +4 -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 +540 -98
- package/daemon/dist/http/routes/system.js.map +1 -1
- package/daemon/dist/index.js +1 -1
- package/daemon/package.json +83 -0
- 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 +44 -14
- package/dist/computer.js.map +1 -1
- package/dist/daemon-manage.js +9 -2
- package/dist/daemon-manage.js.map +1 -1
- package/dist/doctor.js +261 -14
- package/dist/doctor.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,12 +10,15 @@ 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';
|
|
14
|
+
import { getCodingCliStatusResponse, startCodingCliStatusCache, } from '../../core/coding-cli-status-cache.js';
|
|
13
15
|
import { getGatewayInstance, getLlmToken } from '../../core/gateway.js';
|
|
14
16
|
import { normalizeGatewayUplinkUrl, readGatewayConfig, writeGatewayConfig, } from '../../core/gateway-config.js';
|
|
15
17
|
import { ensureSession, getAllAgents, getDailyUsage, getGlobalUsage, getSessionsForAgent, getSessionUiEvents, updateSessionBackendDriverId, updateSessionSdkId, } from '../../core/store.js';
|
|
16
18
|
import { listMcpEnvKeys, listMcpServers } from '../../mcp/default-servers.js';
|
|
17
19
|
import { getMcpEnvOverrides, updateMcpEnvOverrides } from '../../mcp/settings.js';
|
|
18
20
|
import { getMcpManager } from '../../mcp/index.js';
|
|
21
|
+
import { logger } from '../../core/logger.js';
|
|
19
22
|
import { buildDaemonOpenApiSpec } from '../../channels/http-routes.js';
|
|
20
23
|
import { writeJson, writeProtocolError, readJsonBody } from '../response.js';
|
|
21
24
|
import { listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
|
|
@@ -38,6 +41,10 @@ const MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES = 8 * 1024 * 1024;
|
|
|
38
41
|
const MIRRORED_THREAD_RUNNING_LEASE_MS = 30 * 60 * 1000;
|
|
39
42
|
const MIRRORED_THREAD_HISTORY_CHUNK_BYTES = 1024 * 1024;
|
|
40
43
|
const require = createRequire(import.meta.url);
|
|
44
|
+
const HOST_DAEMON_INSTALL_DIR = path.join(SUPEN_HOME, 'daemon');
|
|
45
|
+
const HOST_DAEMON_CLI_PACKAGE_ROOT = path.join(HOST_DAEMON_INSTALL_DIR, 'node_modules', '@supen-ai', 'cli');
|
|
46
|
+
const HOST_DAEMON_BIN_PATH = path.join(HOST_DAEMON_CLI_PACKAGE_ROOT, 'daemon', 'scripts', 'supen-daemon.js');
|
|
47
|
+
const HOST_DAEMON_PACKAGE_SPEC = '@supen-ai/cli';
|
|
41
48
|
function readSqliteRows(dbPath, query) {
|
|
42
49
|
try {
|
|
43
50
|
const { DatabaseSync } = require('node:sqlite');
|
|
@@ -53,6 +60,33 @@ function readSqliteRows(dbPath, query) {
|
|
|
53
60
|
return null;
|
|
54
61
|
}
|
|
55
62
|
}
|
|
63
|
+
function quoteSqliteString(value) {
|
|
64
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
65
|
+
}
|
|
66
|
+
function runSqliteStatement(dbPath, query, params = []) {
|
|
67
|
+
try {
|
|
68
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
69
|
+
const db = new DatabaseSync(dbPath);
|
|
70
|
+
try {
|
|
71
|
+
db.prepare(query).run(...params);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
db.close();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
const expandedQuery = params.reduce((sql, param) => {
|
|
80
|
+
const value = typeof param === 'string' ? quoteSqliteString(param) : String(param);
|
|
81
|
+
return sql.replace('?', value);
|
|
82
|
+
}, query);
|
|
83
|
+
const result = spawnSync('sqlite3', [dbPath, expandedQuery], {
|
|
84
|
+
encoding: 'utf-8',
|
|
85
|
+
maxBuffer: 1024 * 1024,
|
|
86
|
+
});
|
|
87
|
+
return result.status === 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
56
90
|
function gatewayUplinkStatus(config = readGatewayConfig()) {
|
|
57
91
|
const gatewayUrl = (config.gateway_url || '').trim();
|
|
58
92
|
return {
|
|
@@ -218,7 +252,7 @@ function summarizeUiChunk(chunk) {
|
|
|
218
252
|
: 'Unknown error';
|
|
219
253
|
return { category: 'error', summary: truncateText(errorText) };
|
|
220
254
|
}
|
|
221
|
-
if (type === 'data-
|
|
255
|
+
if (type === 'data-codex-event') {
|
|
222
256
|
const data = chunk.data && typeof chunk.data === 'object'
|
|
223
257
|
? chunk.data
|
|
224
258
|
: {};
|
|
@@ -476,6 +510,15 @@ function readThreadIndexEntry(threadId) {
|
|
|
476
510
|
function readThreadStateEntry(threadId) {
|
|
477
511
|
return readThreadStateEntries(1000).find((entry) => entry.id === threadId) || null;
|
|
478
512
|
}
|
|
513
|
+
function archiveMirroredThread(threadId) {
|
|
514
|
+
const dbPath = path.join(localAgentHome(), 'state_5.sqlite');
|
|
515
|
+
if (!threadId || !fs.existsSync(dbPath))
|
|
516
|
+
return false;
|
|
517
|
+
const existing = readThreadStateEntry(threadId);
|
|
518
|
+
if (!existing)
|
|
519
|
+
return false;
|
|
520
|
+
return runSqliteStatement(dbPath, 'update threads set archived = 1 where id = ?', [threadId]);
|
|
521
|
+
}
|
|
479
522
|
function isoFromMillis(value) {
|
|
480
523
|
const ms = typeof value === 'number' ? value : Number(value);
|
|
481
524
|
if (!Number.isFinite(ms) || ms <= 0)
|
|
@@ -757,6 +800,66 @@ function isMirrorableCodexWorkspace(projectPath) {
|
|
|
757
800
|
}
|
|
758
801
|
return !isPathWithin(path.join(SUPEN_HOME, 'tasks'), projectPath);
|
|
759
802
|
}
|
|
803
|
+
function mimeTypeForWorkspaceFile(filePath) {
|
|
804
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
805
|
+
if (ext === '.md' || ext === '.markdown' || ext === '.mdx')
|
|
806
|
+
return 'text/markdown; charset=utf-8';
|
|
807
|
+
if (ext === '.txt' || ext === '.log' || ext === '.env')
|
|
808
|
+
return 'text/plain; charset=utf-8';
|
|
809
|
+
if (ext === '.json')
|
|
810
|
+
return 'application/json; charset=utf-8';
|
|
811
|
+
if (ext === '.html' || ext === '.htm')
|
|
812
|
+
return 'text/html; charset=utf-8';
|
|
813
|
+
if (ext === '.css')
|
|
814
|
+
return 'text/css; charset=utf-8';
|
|
815
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs')
|
|
816
|
+
return 'text/javascript; charset=utf-8';
|
|
817
|
+
if (ext === '.ts' || ext === '.tsx' || ext === '.jsx')
|
|
818
|
+
return 'text/plain; charset=utf-8';
|
|
819
|
+
if (ext === '.png')
|
|
820
|
+
return 'image/png';
|
|
821
|
+
if (ext === '.jpg' || ext === '.jpeg')
|
|
822
|
+
return 'image/jpeg';
|
|
823
|
+
if (ext === '.gif')
|
|
824
|
+
return 'image/gif';
|
|
825
|
+
if (ext === '.webp')
|
|
826
|
+
return 'image/webp';
|
|
827
|
+
if (ext === '.svg')
|
|
828
|
+
return 'image/svg+xml';
|
|
829
|
+
if (ext === '.pdf')
|
|
830
|
+
return 'application/pdf';
|
|
831
|
+
return 'application/octet-stream';
|
|
832
|
+
}
|
|
833
|
+
function serveRemoteFile(req, res, filePath) {
|
|
834
|
+
const stat = fs.statSync(filePath);
|
|
835
|
+
const basename = path.basename(filePath);
|
|
836
|
+
res.writeHead(200, {
|
|
837
|
+
'Content-Type': mimeTypeForWorkspaceFile(filePath),
|
|
838
|
+
'Content-Length': stat.size,
|
|
839
|
+
'Last-Modified': stat.mtime.toUTCString(),
|
|
840
|
+
'Cache-Control': 'no-store',
|
|
841
|
+
'Content-Disposition': `inline; filename="${basename.replace(/"/g, '')}"`,
|
|
842
|
+
});
|
|
843
|
+
if (req.method === 'HEAD') {
|
|
844
|
+
res.end();
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
fs.createReadStream(filePath).pipe(res);
|
|
848
|
+
}
|
|
849
|
+
function resolveRemoteFilePath(rawPath) {
|
|
850
|
+
const trimmed = rawPath.trim();
|
|
851
|
+
if (!trimmed)
|
|
852
|
+
return null;
|
|
853
|
+
const expanded = trimmed === '~'
|
|
854
|
+
? os.homedir()
|
|
855
|
+
: trimmed.startsWith('~/')
|
|
856
|
+
? path.join(os.homedir(), trimmed.slice(2))
|
|
857
|
+
: trimmed;
|
|
858
|
+
const resolved = path.resolve(expanded);
|
|
859
|
+
if (!path.isAbsolute(resolved))
|
|
860
|
+
return null;
|
|
861
|
+
return resolved;
|
|
862
|
+
}
|
|
760
863
|
function isAutomationRunThreadTitle(title) {
|
|
761
864
|
if (!title)
|
|
762
865
|
return false;
|
|
@@ -880,7 +983,9 @@ function sanitizeMirroredHistoryPayload(value, depth = 0) {
|
|
|
880
983
|
}
|
|
881
984
|
function safeMirroredHistoryAttachmentUrl(url) {
|
|
882
985
|
const trimmed = url.trim();
|
|
883
|
-
if (trimmed.startsWith('data:') &&
|
|
986
|
+
if (trimmed.startsWith('data:') &&
|
|
987
|
+
!trimmed.startsWith('data:image/') &&
|
|
988
|
+
trimmed.length > MIRRORED_THREAD_INLINE_DATA_URL_LIMIT) {
|
|
884
989
|
return '';
|
|
885
990
|
}
|
|
886
991
|
return trimmed;
|
|
@@ -902,12 +1007,15 @@ function imageAttachmentFromUrl(url, index) {
|
|
|
902
1007
|
const trimmed = url.trim();
|
|
903
1008
|
if (!trimmed)
|
|
904
1009
|
return null;
|
|
1010
|
+
const safeUrl = safeMirroredHistoryAttachmentUrl(trimmed);
|
|
1011
|
+
if (!safeUrl)
|
|
1012
|
+
return null;
|
|
905
1013
|
const mimeType = mimeTypeFromImageUrl(trimmed);
|
|
906
1014
|
const filename = `pasted-image-${index + 1}.${imageExtensionFromMimeType(mimeType)}`;
|
|
907
1015
|
return {
|
|
908
1016
|
name: filename,
|
|
909
1017
|
filename,
|
|
910
|
-
url:
|
|
1018
|
+
url: safeUrl,
|
|
911
1019
|
mimeType,
|
|
912
1020
|
mime_type: mimeType,
|
|
913
1021
|
};
|
|
@@ -1079,7 +1187,7 @@ function codexReplayEvent(eventId, timestamp, taskId, raw) {
|
|
|
1079
1187
|
timestamp,
|
|
1080
1188
|
task_id: taskId,
|
|
1081
1189
|
chunk: {
|
|
1082
|
-
type: 'data-
|
|
1190
|
+
type: 'data-codex-event',
|
|
1083
1191
|
id: eventId,
|
|
1084
1192
|
data: { raw },
|
|
1085
1193
|
},
|
|
@@ -1158,6 +1266,49 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1158
1266
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
1159
1267
|
});
|
|
1160
1268
|
};
|
|
1269
|
+
const pushWebUserMessage = (event) => {
|
|
1270
|
+
if (event.source !== 'web' || event.event_type !== 'user_message')
|
|
1271
|
+
return;
|
|
1272
|
+
const payload = event.raw_payload && typeof event.raw_payload === 'object'
|
|
1273
|
+
? event.raw_payload
|
|
1274
|
+
: {};
|
|
1275
|
+
const text = typeof payload.content === 'string' ? payload.content : '';
|
|
1276
|
+
const attachments = Array.isArray(payload.attachments)
|
|
1277
|
+
? payload.attachments
|
|
1278
|
+
.map((attachment, index) => {
|
|
1279
|
+
if (!attachment || typeof attachment !== 'object')
|
|
1280
|
+
return null;
|
|
1281
|
+
const record = attachment;
|
|
1282
|
+
const url = typeof record.url === 'string' ? record.url :
|
|
1283
|
+
typeof record.supenUrl === 'string' ? record.supenUrl :
|
|
1284
|
+
typeof record.path === 'string' ? record.path :
|
|
1285
|
+
typeof record.taskPath === 'string' ? record.taskPath :
|
|
1286
|
+
'';
|
|
1287
|
+
if (!url.trim())
|
|
1288
|
+
return null;
|
|
1289
|
+
const filename = typeof record.filename === 'string' ? record.filename :
|
|
1290
|
+
typeof record.name === 'string' ? record.name :
|
|
1291
|
+
`attachment-${index + 1}`;
|
|
1292
|
+
const mimeType = typeof record.mimeType === 'string' ? record.mimeType :
|
|
1293
|
+
typeof record.mime_type === 'string' ? record.mime_type :
|
|
1294
|
+
'application/octet-stream';
|
|
1295
|
+
const safeUrl = safeMirroredHistoryAttachmentUrl(url);
|
|
1296
|
+
if (!safeUrl)
|
|
1297
|
+
return null;
|
|
1298
|
+
return {
|
|
1299
|
+
name: filename,
|
|
1300
|
+
filename,
|
|
1301
|
+
url: safeUrl,
|
|
1302
|
+
mimeType,
|
|
1303
|
+
mime_type: mimeType,
|
|
1304
|
+
};
|
|
1305
|
+
})
|
|
1306
|
+
.filter((attachment) => Boolean(attachment))
|
|
1307
|
+
: [];
|
|
1308
|
+
const timestamp = typeof payload.timestamp === 'string' ? payload.timestamp : event.received_at;
|
|
1309
|
+
const messageId = typeof payload.id === 'string' ? payload.id : `${threadId}-web-user-${event.sequence}`;
|
|
1310
|
+
pushUserMessage(text, timestamp, messageId, attachments);
|
|
1311
|
+
};
|
|
1161
1312
|
const pushEvent = (timestamp, chunk) => {
|
|
1162
1313
|
eventIndex += 1;
|
|
1163
1314
|
events.push({
|
|
@@ -1368,15 +1519,20 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1368
1519
|
task_id: threadId,
|
|
1369
1520
|
});
|
|
1370
1521
|
}
|
|
1371
|
-
|
|
1522
|
+
const eventLogEvents = listRecentThreadEventsAfter(eventLogThreadId, 0, {
|
|
1372
1523
|
limit: MIRRORED_THREAD_STREAM_REPLAY_LIMIT,
|
|
1373
1524
|
maxBytes: MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES,
|
|
1374
|
-
})
|
|
1525
|
+
});
|
|
1526
|
+
for (const event of eventLogEvents) {
|
|
1527
|
+
pushWebUserMessage(event);
|
|
1528
|
+
}
|
|
1529
|
+
for (const event of eventLogEvents) {
|
|
1375
1530
|
const chunk = threadStreamChunkForEvent(event);
|
|
1376
1531
|
if (!chunk)
|
|
1377
1532
|
continue;
|
|
1378
1533
|
events.push({
|
|
1379
1534
|
id: `${threadId}-event-log-${event.sequence}`,
|
|
1535
|
+
sequence: event.sequence,
|
|
1380
1536
|
timestamp: event.received_at,
|
|
1381
1537
|
task_id: threadId,
|
|
1382
1538
|
chunk,
|
|
@@ -1539,17 +1695,41 @@ function threadStreamChunkForEvent(event) {
|
|
|
1539
1695
|
return null;
|
|
1540
1696
|
if (event.raw_payload &&
|
|
1541
1697
|
typeof event.raw_payload === 'object' &&
|
|
1542
|
-
!Array.isArray(event.raw_payload)
|
|
1543
|
-
|
|
1698
|
+
!Array.isArray(event.raw_payload) &&
|
|
1699
|
+
typeof event.raw_payload.type === 'string' &&
|
|
1700
|
+
String(event.raw_payload.type).trim()) {
|
|
1701
|
+
return normalizeStoredCodexThreadChunk(event.raw_payload);
|
|
1544
1702
|
}
|
|
1545
1703
|
return {
|
|
1546
|
-
type: 'data-
|
|
1704
|
+
type: 'data-codex-event',
|
|
1547
1705
|
data: {
|
|
1548
1706
|
eventType: event.event_type,
|
|
1549
1707
|
raw: event.raw_payload,
|
|
1550
1708
|
},
|
|
1551
1709
|
};
|
|
1552
1710
|
}
|
|
1711
|
+
function normalizeStoredCodexThreadChunk(chunk) {
|
|
1712
|
+
if (chunk.type !== 'data-supen-event')
|
|
1713
|
+
return chunk;
|
|
1714
|
+
const data = chunk.data && typeof chunk.data === 'object' && !Array.isArray(chunk.data)
|
|
1715
|
+
? chunk.data
|
|
1716
|
+
: {};
|
|
1717
|
+
const raw = data.raw && typeof data.raw === 'object' && !Array.isArray(data.raw)
|
|
1718
|
+
? data.raw
|
|
1719
|
+
: {};
|
|
1720
|
+
return {
|
|
1721
|
+
...chunk,
|
|
1722
|
+
type: 'data-codex-event',
|
|
1723
|
+
data: {
|
|
1724
|
+
...data,
|
|
1725
|
+
eventType: typeof data.eventType === 'string' && data.eventType.trim()
|
|
1726
|
+
? data.eventType
|
|
1727
|
+
: typeof raw.method === 'string' && raw.method.trim()
|
|
1728
|
+
? raw.method
|
|
1729
|
+
: 'codex-event',
|
|
1730
|
+
},
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1553
1733
|
function buildMcpSettingsResponse() {
|
|
1554
1734
|
const overrides = getMcpEnvOverrides();
|
|
1555
1735
|
const runtimeEnv = { ...process.env };
|
|
@@ -1653,19 +1833,206 @@ function codexConnectSnapshot() {
|
|
|
1653
1833
|
error: codexConnectRuntime.error,
|
|
1654
1834
|
};
|
|
1655
1835
|
}
|
|
1836
|
+
function detectGlobalNpmRoot() {
|
|
1837
|
+
const result = spawnSync('npm', ['root', '-g'], {
|
|
1838
|
+
encoding: 'utf8',
|
|
1839
|
+
timeout: 3000,
|
|
1840
|
+
});
|
|
1841
|
+
if (result.status !== 0)
|
|
1842
|
+
return null;
|
|
1843
|
+
const root = String(result.stdout || '').trim();
|
|
1844
|
+
if (!root)
|
|
1845
|
+
return null;
|
|
1846
|
+
try {
|
|
1847
|
+
return fs.realpathSync(root);
|
|
1848
|
+
}
|
|
1849
|
+
catch {
|
|
1850
|
+
return root;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
function findPackageRootFrom(startPath, expectedName) {
|
|
1854
|
+
if (!startPath)
|
|
1855
|
+
return null;
|
|
1856
|
+
let current = fs.existsSync(startPath) && fs.statSync(startPath).isDirectory()
|
|
1857
|
+
? startPath
|
|
1858
|
+
: path.dirname(startPath);
|
|
1859
|
+
for (let i = 0; i < 8; i += 1) {
|
|
1860
|
+
const packageJsonPath = path.join(current, 'package.json');
|
|
1861
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1862
|
+
try {
|
|
1863
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1864
|
+
if (parsed.name === expectedName)
|
|
1865
|
+
return current;
|
|
1866
|
+
}
|
|
1867
|
+
catch {
|
|
1868
|
+
return null;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
const next = path.dirname(current);
|
|
1872
|
+
if (next === current)
|
|
1873
|
+
break;
|
|
1874
|
+
current = next;
|
|
1875
|
+
}
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
function readPackageVersion(packageRoot) {
|
|
1879
|
+
if (!packageRoot)
|
|
1880
|
+
return null;
|
|
1881
|
+
try {
|
|
1882
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
1883
|
+
return typeof parsed.version === 'string' ? parsed.version : null;
|
|
1884
|
+
}
|
|
1885
|
+
catch {
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
function detectDaemonPackageStatus() {
|
|
1890
|
+
const envVersion = process.env.SUPEN_DAEMON_VERSION || process.env.npm_package_version || null;
|
|
1891
|
+
let packageRoot = null;
|
|
1892
|
+
try {
|
|
1893
|
+
packageRoot = findPackageRootFrom(require.resolve('@supen-ai/daemon'), '@supen-ai/daemon');
|
|
1894
|
+
}
|
|
1895
|
+
catch {
|
|
1896
|
+
packageRoot = null;
|
|
1897
|
+
}
|
|
1898
|
+
if (!packageRoot) {
|
|
1899
|
+
for (const candidate of [process.argv[1], process.cwd(), path.resolve(process.cwd(), 'apps/daemon')]) {
|
|
1900
|
+
packageRoot = findPackageRootFrom(candidate || null, '@supen-ai/daemon');
|
|
1901
|
+
if (packageRoot)
|
|
1902
|
+
break;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
let cliPackageRoot = null;
|
|
1906
|
+
try {
|
|
1907
|
+
if (fs.existsSync(path.join(HOST_DAEMON_CLI_PACKAGE_ROOT, 'package.json'))) {
|
|
1908
|
+
cliPackageRoot = fs.realpathSync(HOST_DAEMON_CLI_PACKAGE_ROOT);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
catch {
|
|
1912
|
+
cliPackageRoot = null;
|
|
1913
|
+
}
|
|
1914
|
+
if (!cliPackageRoot) {
|
|
1915
|
+
cliPackageRoot = findPackageRootFrom(process.argv[1] || null, '@supen-ai/cli');
|
|
1916
|
+
}
|
|
1917
|
+
const bundledDaemonRoot = cliPackageRoot
|
|
1918
|
+
? path.join(cliPackageRoot, 'daemon')
|
|
1919
|
+
: null;
|
|
1920
|
+
const bundledDaemonVersion = readPackageVersion(bundledDaemonRoot);
|
|
1921
|
+
const packageVersion = readPackageVersion(packageRoot);
|
|
1922
|
+
const npmRoot = detectGlobalNpmRoot();
|
|
1923
|
+
const realPackageRoot = packageRoot ? fs.realpathSync(packageRoot) : null;
|
|
1924
|
+
const realCliPackageRoot = cliPackageRoot ? fs.realpathSync(cliPackageRoot) : null;
|
|
1925
|
+
const installSource = realCliPackageRoot && realCliPackageRoot.startsWith(`${HOST_DAEMON_INSTALL_DIR}${path.sep}`)
|
|
1926
|
+
? 'supen-cli-bundle'
|
|
1927
|
+
: realPackageRoot && npmRoot && realPackageRoot.startsWith(`${npmRoot}${path.sep}@supen-ai${path.sep}daemon`)
|
|
1928
|
+
? 'npm-global'
|
|
1929
|
+
: packageRoot
|
|
1930
|
+
? 'local-package'
|
|
1931
|
+
: null;
|
|
1932
|
+
return {
|
|
1933
|
+
version: envVersion || bundledDaemonVersion || packageVersion,
|
|
1934
|
+
package_root: packageRoot || bundledDaemonRoot,
|
|
1935
|
+
install_source: installSource,
|
|
1936
|
+
update_supported: installSource === 'supen-cli-bundle' || installSource === 'npm-global',
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function restartManagedHostDaemon() {
|
|
1940
|
+
if (process.platform === 'linux') {
|
|
1941
|
+
const result = spawnSync('systemctl', ['--user', 'restart', 'supen-daemon.service'], {
|
|
1942
|
+
encoding: 'utf8',
|
|
1943
|
+
timeout: 10_000,
|
|
1944
|
+
});
|
|
1945
|
+
if (result.status === 0)
|
|
1946
|
+
return { method: 'systemd', attempted: true };
|
|
1947
|
+
}
|
|
1948
|
+
if (process.platform === 'darwin') {
|
|
1949
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : Number(process.env.UID || 0);
|
|
1950
|
+
const result = spawnSync('launchctl', ['kickstart', '-k', `gui/${uid}/ai.supen.daemon`], {
|
|
1951
|
+
encoding: 'utf8',
|
|
1952
|
+
timeout: 10_000,
|
|
1953
|
+
});
|
|
1954
|
+
if (result.status === 0)
|
|
1955
|
+
return { method: 'launchd', attempted: true };
|
|
1956
|
+
}
|
|
1957
|
+
if (fs.existsSync(HOST_DAEMON_BIN_PATH)) {
|
|
1958
|
+
const child = spawn(process.execPath, [HOST_DAEMON_BIN_PATH], {
|
|
1959
|
+
detached: true,
|
|
1960
|
+
stdio: 'ignore',
|
|
1961
|
+
env: process.env,
|
|
1962
|
+
});
|
|
1963
|
+
child.unref();
|
|
1964
|
+
setTimeout(() => process.exit(0), 250);
|
|
1965
|
+
return { method: 'detached-node', attempted: true };
|
|
1966
|
+
}
|
|
1967
|
+
return { method: 'none', attempted: false };
|
|
1968
|
+
}
|
|
1969
|
+
function inferCliInstallSource(name, executablePath, resolvedPath) {
|
|
1970
|
+
if (!executablePath && !resolvedPath)
|
|
1971
|
+
return { install_source: null, update_supported: false };
|
|
1972
|
+
const candidates = [executablePath || '', resolvedPath || ''].map((value) => value.toLowerCase());
|
|
1973
|
+
const normalized = candidates.join('\n');
|
|
1974
|
+
if (normalized.includes('/.local/share/pnpm/') || normalized.includes('/pnpm/')) {
|
|
1975
|
+
return { install_source: 'pnpm', update_supported: false };
|
|
1976
|
+
}
|
|
1977
|
+
if (normalized.includes('/homebrew/') || normalized.includes('/opt/homebrew/') || normalized.includes('/cellar/')) {
|
|
1978
|
+
return { install_source: 'homebrew', update_supported: false };
|
|
1979
|
+
}
|
|
1980
|
+
if (name === 'codex') {
|
|
1981
|
+
const npmRoot = detectGlobalNpmRoot();
|
|
1982
|
+
const codexPackagePath = resolvedPath || executablePath || '';
|
|
1983
|
+
if (npmRoot && codexPackagePath.startsWith(`${npmRoot}${path.sep}@openai${path.sep}codex${path.sep}`)) {
|
|
1984
|
+
return { install_source: 'npm-global', update_supported: true };
|
|
1985
|
+
}
|
|
1986
|
+
if (normalized.includes('/node_modules/@openai/codex/')) {
|
|
1987
|
+
return { install_source: 'node-package', update_supported: false };
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
if (name === 'gemini') {
|
|
1991
|
+
const npmRoot = detectGlobalNpmRoot();
|
|
1992
|
+
const geminiPackagePath = resolvedPath || executablePath || '';
|
|
1993
|
+
if (npmRoot && geminiPackagePath.startsWith(`${npmRoot}${path.sep}@google${path.sep}gemini-cli${path.sep}`)) {
|
|
1994
|
+
return { install_source: 'npm-global', update_supported: true };
|
|
1995
|
+
}
|
|
1996
|
+
if (normalized.includes('/node_modules/@google/gemini-cli/')) {
|
|
1997
|
+
return { install_source: 'node-package', update_supported: false };
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
return { install_source: null, update_supported: false };
|
|
2001
|
+
}
|
|
1656
2002
|
function detectCliInstalled(name) {
|
|
1657
|
-
const cmd = spawnSync('sh', ['-
|
|
2003
|
+
const cmd = spawnSync('sh', ['-c', `command -v ${name}`], {
|
|
1658
2004
|
encoding: 'utf8',
|
|
1659
2005
|
timeout: 3000,
|
|
1660
2006
|
});
|
|
1661
|
-
if (cmd.status !== 0)
|
|
1662
|
-
return { installed: false, version: null };
|
|
2007
|
+
if (cmd.status !== 0) {
|
|
2008
|
+
return { installed: false, version: null, path: null, resolved_path: null, install_source: null, update_supported: false };
|
|
2009
|
+
}
|
|
2010
|
+
const executablePath = String(cmd.stdout || '').split(/\r?\n/g).find(Boolean)?.trim() || null;
|
|
2011
|
+
let resolvedPath = null;
|
|
2012
|
+
if (executablePath) {
|
|
2013
|
+
try {
|
|
2014
|
+
resolvedPath = fs.realpathSync(executablePath);
|
|
2015
|
+
}
|
|
2016
|
+
catch {
|
|
2017
|
+
resolvedPath = executablePath;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1663
2020
|
const versionCmd = spawnSync(name, ['--version'], {
|
|
1664
2021
|
encoding: 'utf8',
|
|
1665
2022
|
timeout: 5000,
|
|
1666
2023
|
});
|
|
1667
|
-
const output =
|
|
1668
|
-
|
|
2024
|
+
const output = versionCmd.status === 0
|
|
2025
|
+
? trimLogNoise(`${versionCmd.stdout || ''}\n${versionCmd.stderr || ''}`) || ''
|
|
2026
|
+
: '';
|
|
2027
|
+
const semver = output.match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/)?.[0] || null;
|
|
2028
|
+
const source = inferCliInstallSource(name, executablePath, resolvedPath);
|
|
2029
|
+
return {
|
|
2030
|
+
installed: true,
|
|
2031
|
+
version: semver || (output && !output.includes('\n') ? output : null),
|
|
2032
|
+
path: executablePath,
|
|
2033
|
+
resolved_path: resolvedPath,
|
|
2034
|
+
...source,
|
|
2035
|
+
};
|
|
1669
2036
|
}
|
|
1670
2037
|
function detectCodexAuthStatus(installed) {
|
|
1671
2038
|
if (!installed)
|
|
@@ -1674,14 +2041,27 @@ function detectCodexAuthStatus(installed) {
|
|
|
1674
2041
|
encoding: 'utf8',
|
|
1675
2042
|
timeout: 8000,
|
|
1676
2043
|
});
|
|
1677
|
-
const
|
|
2044
|
+
const lines = `${result.stdout || ''}\n${result.stderr || ''}`
|
|
1678
2045
|
.split(/\r?\n/g)
|
|
1679
2046
|
.map((line) => trimLogNoise(line))
|
|
1680
|
-
.filter(Boolean)
|
|
1681
|
-
|
|
2047
|
+
.filter(Boolean);
|
|
2048
|
+
const text = lines.join('\n');
|
|
1682
2049
|
const authenticated = /logged in/i.test(text) && !/not logged in/i.test(text);
|
|
1683
|
-
const
|
|
1684
|
-
|
|
2050
|
+
const email = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i)?.[0] || null;
|
|
2051
|
+
const accountLine = lines.find((line) => /logged in as|account|email|user/i.test(line) && !line.startsWith('file://')) || null;
|
|
2052
|
+
const summary = authenticated
|
|
2053
|
+
? email || accountLine || 'Authenticated'
|
|
2054
|
+
: null;
|
|
2055
|
+
let accountId = null;
|
|
2056
|
+
try {
|
|
2057
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(resolveCodexHome(), 'auth.json'), 'utf8'));
|
|
2058
|
+
const tokens = parsed.tokens && typeof parsed.tokens === 'object' ? parsed.tokens : {};
|
|
2059
|
+
accountId = typeof tokens.account_id === 'string' && tokens.account_id.trim() ? tokens.account_id.trim() : null;
|
|
2060
|
+
}
|
|
2061
|
+
catch {
|
|
2062
|
+
accountId = null;
|
|
2063
|
+
}
|
|
2064
|
+
return { authenticated, summary, account_id: accountId };
|
|
1685
2065
|
}
|
|
1686
2066
|
function readCodexDefaultModel() {
|
|
1687
2067
|
const codexHome = resolveCodexHome();
|
|
@@ -1799,6 +2179,10 @@ function readCodingCliStatusPayload() {
|
|
|
1799
2179
|
name,
|
|
1800
2180
|
installed: detected.installed,
|
|
1801
2181
|
version: detected.version,
|
|
2182
|
+
path: detected.path,
|
|
2183
|
+
resolved_path: detected.resolved_path,
|
|
2184
|
+
install_source: detected.install_source,
|
|
2185
|
+
update_supported: detected.update_supported,
|
|
1802
2186
|
};
|
|
1803
2187
|
});
|
|
1804
2188
|
const codexInstalled = clis.find((cli) => cli.name === 'codex')?.installed ?? false;
|
|
@@ -1819,6 +2203,10 @@ function readCodingCliStatusPayload() {
|
|
|
1819
2203
|
installed: detected.installed,
|
|
1820
2204
|
version: detected.version,
|
|
1821
2205
|
managed: app.managed,
|
|
2206
|
+
path: detected.path,
|
|
2207
|
+
resolved_path: detected.resolved_path,
|
|
2208
|
+
install_source: detected.install_source,
|
|
2209
|
+
update_supported: detected.update_supported,
|
|
1822
2210
|
};
|
|
1823
2211
|
});
|
|
1824
2212
|
return {
|
|
@@ -1889,6 +2277,15 @@ function readCodingCliStatusPayload() {
|
|
|
1889
2277
|
codex_connect: codexConnectSnapshot(),
|
|
1890
2278
|
};
|
|
1891
2279
|
}
|
|
2280
|
+
function readCachedCodingCliStatusPayload(options) {
|
|
2281
|
+
startCodingCliStatusCache({
|
|
2282
|
+
build: () => ({
|
|
2283
|
+
...readCodingCliStatusPayload(),
|
|
2284
|
+
daemon: detectDaemonPackageStatus(),
|
|
2285
|
+
}),
|
|
2286
|
+
});
|
|
2287
|
+
return getCodingCliStatusResponse(options);
|
|
2288
|
+
}
|
|
1892
2289
|
function readRuntimeModelStatusPayload() {
|
|
1893
2290
|
const selected_cli = process.env.SUPEN_CODING_CLI || readConfigSummary().coding_cli || 'codex';
|
|
1894
2291
|
const codexTransport = normalizeCodexTransport(process.env.SUPEN_CODEX_TRANSPORT) ||
|
|
@@ -2017,15 +2414,15 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2017
2414
|
writeJson(res, 200, { status: 'ok' });
|
|
2018
2415
|
return true;
|
|
2019
2416
|
}
|
|
2020
|
-
if (pathname === '/api/
|
|
2417
|
+
if (pathname === '/api/computers/{computer_id}/openapi.json' && method === 'GET') {
|
|
2021
2418
|
writeJson(res, 200, buildDaemonOpenApiSpec());
|
|
2022
2419
|
return true;
|
|
2023
2420
|
}
|
|
2024
|
-
if (pathname === '/api/
|
|
2421
|
+
if (pathname === '/api/computers/{computer_id}/models' && method === 'GET') {
|
|
2025
2422
|
writeJson(res, 200, { models: MODELS_REGISTRY });
|
|
2026
2423
|
return true;
|
|
2027
2424
|
}
|
|
2028
|
-
if (pathname === '/api/
|
|
2425
|
+
if (pathname === '/api/computers/{computer_id}/config/reload' && method === 'POST') {
|
|
2029
2426
|
const before = {
|
|
2030
2427
|
total_models: MODELS_REGISTRY.length,
|
|
2031
2428
|
default_model: DEFAULT_MODEL?.id ?? null,
|
|
@@ -2041,11 +2438,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2041
2438
|
});
|
|
2042
2439
|
return true;
|
|
2043
2440
|
}
|
|
2044
|
-
if (pathname === '/api/
|
|
2441
|
+
if (pathname === '/api/computers/{computer_id}/config-summary' && method === 'GET') {
|
|
2045
2442
|
writeJson(res, 200, readConfigSummary());
|
|
2046
2443
|
return true;
|
|
2047
2444
|
}
|
|
2048
|
-
if (pathname === '/api/
|
|
2445
|
+
if (pathname === '/api/computers/{computer_id}/logs' && method === 'GET') {
|
|
2049
2446
|
const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
|
|
2050
2447
|
const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
|
|
2051
2448
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
@@ -2057,7 +2454,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2057
2454
|
}));
|
|
2058
2455
|
return true;
|
|
2059
2456
|
}
|
|
2060
|
-
if (pathname === '/api/
|
|
2457
|
+
if (pathname === '/api/computers/{computer_id}/logs/stream' && method === 'GET') {
|
|
2061
2458
|
const agentId = coerceSingleQueryParam(url.searchParams.get('agent_id'));
|
|
2062
2459
|
const sessionId = coerceSingleQueryParam(url.searchParams.get('session_id'));
|
|
2063
2460
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
@@ -2100,7 +2497,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2100
2497
|
writeSse(res);
|
|
2101
2498
|
return true;
|
|
2102
2499
|
}
|
|
2103
|
-
if (pathname === '/api/
|
|
2500
|
+
if (pathname === '/api/computers/{computer_id}/config-yaml' && method === 'GET') {
|
|
2104
2501
|
try {
|
|
2105
2502
|
const yaml = readConfigYamlFile();
|
|
2106
2503
|
writeJson(res, 200, { yaml });
|
|
@@ -2110,7 +2507,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2110
2507
|
}
|
|
2111
2508
|
return true;
|
|
2112
2509
|
}
|
|
2113
|
-
if (pathname === '/api/
|
|
2510
|
+
if (pathname === '/api/computers/{computer_id}/config-yaml' && method === 'PUT') {
|
|
2114
2511
|
try {
|
|
2115
2512
|
const parsed = await readJsonBody(req);
|
|
2116
2513
|
const yamlText = typeof parsed === 'object' && parsed && typeof parsed.yaml === 'string'
|
|
@@ -2137,11 +2534,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2137
2534
|
}
|
|
2138
2535
|
return true;
|
|
2139
2536
|
}
|
|
2140
|
-
if (pathname === '/api/
|
|
2537
|
+
if (pathname === '/api/computers/{computer_id}/gateway-uplink' && method === 'GET') {
|
|
2141
2538
|
writeJson(res, 200, gatewayUplinkStatus());
|
|
2142
2539
|
return true;
|
|
2143
2540
|
}
|
|
2144
|
-
if (pathname === '/api/
|
|
2541
|
+
if (pathname === '/api/computers/{computer_id}/gateway-uplink' && (method === 'PUT' || method === 'PATCH')) {
|
|
2145
2542
|
if (process.env.SUPEN_GATEWAY_URL) {
|
|
2146
2543
|
writeProtocolError(res, 409, 'config_error', 'env_override', 'SUPEN_GATEWAY_URL is set; update the daemon environment to change the gateway uplink.');
|
|
2147
2544
|
return true;
|
|
@@ -2184,11 +2581,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2184
2581
|
}
|
|
2185
2582
|
return true;
|
|
2186
2583
|
}
|
|
2187
|
-
if (pathname === '/api/
|
|
2584
|
+
if (pathname === '/api/computers/{computer_id}/env' && method === 'GET') {
|
|
2188
2585
|
writeJson(res, 200, { env: readSpaceEnvMap() });
|
|
2189
2586
|
return true;
|
|
2190
2587
|
}
|
|
2191
|
-
if (pathname === '/api/
|
|
2588
|
+
if (pathname === '/api/computers/{computer_id}/llm-env' && method === 'GET') {
|
|
2192
2589
|
const token = getLlmToken() || process.env.SUPEN_LLM_TOKEN || process.env.SUPEN_LLM_API_KEY || '';
|
|
2193
2590
|
const env = withSupenLlmEnv({
|
|
2194
2591
|
...process.env,
|
|
@@ -2208,15 +2605,15 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2208
2605
|
});
|
|
2209
2606
|
return true;
|
|
2210
2607
|
}
|
|
2211
|
-
if (pathname === '/api/
|
|
2212
|
-
writeJson(res, 200,
|
|
2608
|
+
if (pathname === '/api/computers/{computer_id}/apps' && method === 'GET') {
|
|
2609
|
+
writeJson(res, 200, readCachedCodingCliStatusPayload());
|
|
2213
2610
|
return true;
|
|
2214
2611
|
}
|
|
2215
|
-
if (pathname === '/api/
|
|
2612
|
+
if (pathname === '/api/computers/{computer_id}/runtime-models' && method === 'GET') {
|
|
2216
2613
|
writeJson(res, 200, readRuntimeModelStatusPayload());
|
|
2217
2614
|
return true;
|
|
2218
2615
|
}
|
|
2219
|
-
if (pathname === '/api/
|
|
2616
|
+
if (pathname === '/api/computers/{computer_id}/runtime-models/default' && method === 'PUT') {
|
|
2220
2617
|
try {
|
|
2221
2618
|
const parsed = await readJsonBody(req);
|
|
2222
2619
|
const model = typeof parsed === 'object' && parsed && typeof parsed.model === 'string'
|
|
@@ -2237,7 +2634,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2237
2634
|
}
|
|
2238
2635
|
return true;
|
|
2239
2636
|
}
|
|
2240
|
-
const spaceCodexTransportDefaultMatch = pathname.match(/^\/api\/
|
|
2637
|
+
const spaceCodexTransportDefaultMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/codex\/transports\/([^/]+)\/default$/);
|
|
2241
2638
|
if (spaceCodexTransportDefaultMatch && method === 'PUT') {
|
|
2242
2639
|
try {
|
|
2243
2640
|
const transport = normalizeCodexTransport(decodeURIComponent(spaceCodexTransportDefaultMatch[1] || '').trim());
|
|
@@ -2249,7 +2646,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2249
2646
|
writeJson(res, 200, {
|
|
2250
2647
|
ok: true,
|
|
2251
2648
|
...result,
|
|
2252
|
-
status:
|
|
2649
|
+
status: readCachedCodingCliStatusPayload({ force: true }),
|
|
2253
2650
|
});
|
|
2254
2651
|
}
|
|
2255
2652
|
catch (err) {
|
|
@@ -2257,7 +2654,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2257
2654
|
}
|
|
2258
2655
|
return true;
|
|
2259
2656
|
}
|
|
2260
|
-
const spaceAppDefaultMatch = pathname.match(/^\/api\/
|
|
2657
|
+
const spaceAppDefaultMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/default$/);
|
|
2261
2658
|
if (spaceAppDefaultMatch && method === 'PUT') {
|
|
2262
2659
|
try {
|
|
2263
2660
|
const cli = normalizeCliName(decodeURIComponent(spaceAppDefaultMatch[1] || '').trim());
|
|
@@ -2269,7 +2666,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2269
2666
|
writeJson(res, 200, {
|
|
2270
2667
|
ok: true,
|
|
2271
2668
|
...result,
|
|
2272
|
-
status:
|
|
2669
|
+
status: readCachedCodingCliStatusPayload({ force: true }),
|
|
2273
2670
|
});
|
|
2274
2671
|
}
|
|
2275
2672
|
catch (err) {
|
|
@@ -2277,7 +2674,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2277
2674
|
}
|
|
2278
2675
|
return true;
|
|
2279
2676
|
}
|
|
2280
|
-
const spaceAppInstallMatch = pathname.match(/^\/api\/
|
|
2677
|
+
const spaceAppInstallMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/install$/);
|
|
2281
2678
|
if (spaceAppInstallMatch && method === 'POST') {
|
|
2282
2679
|
try {
|
|
2283
2680
|
const cli = normalizeCliName(decodeURIComponent(spaceAppInstallMatch[1] || '').trim());
|
|
@@ -2289,6 +2686,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2289
2686
|
writeProtocolError(res, 400, 'validation_error', 'install_not_supported', 'Only codex and gemini support managed install at the moment');
|
|
2290
2687
|
return true;
|
|
2291
2688
|
}
|
|
2689
|
+
const current = detectCliInstalled(cli);
|
|
2690
|
+
if (current.installed && !current.update_supported) {
|
|
2691
|
+
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.`);
|
|
2692
|
+
return true;
|
|
2693
|
+
}
|
|
2292
2694
|
const installSpec = MANAGED_CODING_CLI_INSTALL_COMMANDS[cli];
|
|
2293
2695
|
const result = spawnSync(installSpec.command, installSpec.args, {
|
|
2294
2696
|
encoding: 'utf8',
|
|
@@ -2302,7 +2704,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2302
2704
|
writeJson(res, 200, {
|
|
2303
2705
|
ok: true,
|
|
2304
2706
|
cli,
|
|
2305
|
-
status:
|
|
2707
|
+
status: readCachedCodingCliStatusPayload({ force: true }),
|
|
2306
2708
|
});
|
|
2307
2709
|
}
|
|
2308
2710
|
catch (err) {
|
|
@@ -2310,14 +2712,51 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2310
2712
|
}
|
|
2311
2713
|
return true;
|
|
2312
2714
|
}
|
|
2313
|
-
|
|
2715
|
+
if (pathname === '/api/computers/{computer_id}/daemon/update' && method === 'POST') {
|
|
2716
|
+
try {
|
|
2717
|
+
const current = detectDaemonPackageStatus();
|
|
2718
|
+
if (!current.update_supported) {
|
|
2719
|
+
writeProtocolError(res, 400, 'validation_error', 'daemon_update_not_supported', `Supen daemon is installed via ${current.install_source || current.package_root || 'an unknown source'}; update it with that installer on this computer.`);
|
|
2720
|
+
return true;
|
|
2721
|
+
}
|
|
2722
|
+
fs.mkdirSync(HOST_DAEMON_INSTALL_DIR, { recursive: true });
|
|
2723
|
+
const result = spawnSync('npm', ['install', '--prefix', HOST_DAEMON_INSTALL_DIR, HOST_DAEMON_PACKAGE_SPEC], {
|
|
2724
|
+
encoding: 'utf8',
|
|
2725
|
+
timeout: 120_000,
|
|
2726
|
+
});
|
|
2727
|
+
if (result.status !== 0) {
|
|
2728
|
+
const output = `${result.stdout || ''}\n${result.stderr || ''}`.trim() || 'Failed to update Supen daemon';
|
|
2729
|
+
writeProtocolError(res, 500, 'install_error', 'daemon_update_failed', output);
|
|
2730
|
+
return true;
|
|
2731
|
+
}
|
|
2732
|
+
writeJson(res, 200, {
|
|
2733
|
+
ok: true,
|
|
2734
|
+
daemon: detectDaemonPackageStatus(),
|
|
2735
|
+
status: readCachedCodingCliStatusPayload({ force: true }),
|
|
2736
|
+
restart: { scheduled: true },
|
|
2737
|
+
});
|
|
2738
|
+
setTimeout(() => {
|
|
2739
|
+
try {
|
|
2740
|
+
restartManagedHostDaemon();
|
|
2741
|
+
}
|
|
2742
|
+
catch (err) {
|
|
2743
|
+
logger.error({ err }, 'Failed to restart Supen daemon after update');
|
|
2744
|
+
}
|
|
2745
|
+
}, 250);
|
|
2746
|
+
}
|
|
2747
|
+
catch (err) {
|
|
2748
|
+
writeProtocolError(res, 500, 'install_error', 'daemon_update_failed', err?.message || 'Failed to update Supen daemon');
|
|
2749
|
+
}
|
|
2750
|
+
return true;
|
|
2751
|
+
}
|
|
2752
|
+
const spaceAppConnectMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/connect$/);
|
|
2314
2753
|
if (spaceAppConnectMatch && method === 'POST') {
|
|
2315
2754
|
const cli = normalizeCliName(decodeURIComponent(spaceAppConnectMatch[1] || '').trim());
|
|
2316
2755
|
if (cli !== 'codex') {
|
|
2317
2756
|
writeProtocolError(res, 400, 'validation_error', 'connect_not_supported', 'Only codex supports connect flow at the moment');
|
|
2318
2757
|
return true;
|
|
2319
2758
|
}
|
|
2320
|
-
const status =
|
|
2759
|
+
const status = readCachedCodingCliStatusPayload();
|
|
2321
2760
|
const codex = status.clis.find((entry) => entry.name === 'codex');
|
|
2322
2761
|
if (!codex?.installed) {
|
|
2323
2762
|
writeProtocolError(res, 400, 'validation_error', 'codex_not_installed', 'codex CLI is not installed');
|
|
@@ -2326,7 +2765,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2326
2765
|
writeJson(res, 200, {
|
|
2327
2766
|
ok: true,
|
|
2328
2767
|
codex_connect: startCodexConnectFlow(),
|
|
2329
|
-
status:
|
|
2768
|
+
status: readCachedCodingCliStatusPayload({ force: true }),
|
|
2330
2769
|
});
|
|
2331
2770
|
return true;
|
|
2332
2771
|
}
|
|
@@ -2339,11 +2778,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2339
2778
|
writeJson(res, 200, {
|
|
2340
2779
|
ok: true,
|
|
2341
2780
|
codex_connect: cancelCodexConnectFlow(),
|
|
2342
|
-
status:
|
|
2781
|
+
status: readCachedCodingCliStatusPayload({ force: true }),
|
|
2343
2782
|
});
|
|
2344
2783
|
return true;
|
|
2345
2784
|
}
|
|
2346
|
-
const spaceAppSessionMatch = pathname.match(/^\/api\/
|
|
2785
|
+
const spaceAppSessionMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/session$/);
|
|
2347
2786
|
if (spaceAppSessionMatch && method === 'DELETE') {
|
|
2348
2787
|
const cli = normalizeCliName(decodeURIComponent(spaceAppSessionMatch[1] || '').trim());
|
|
2349
2788
|
if (cli !== 'codex') {
|
|
@@ -2359,10 +2798,10 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2359
2798
|
writeProtocolError(res, 500, 'auth_error', 'codex_logout_failed', output);
|
|
2360
2799
|
return true;
|
|
2361
2800
|
}
|
|
2362
|
-
writeJson(res, 200, { ok: true, status:
|
|
2801
|
+
writeJson(res, 200, { ok: true, status: readCachedCodingCliStatusPayload({ force: true }) });
|
|
2363
2802
|
return true;
|
|
2364
2803
|
}
|
|
2365
|
-
if (pathname === '/api/
|
|
2804
|
+
if (pathname === '/api/computers/{computer_id}/env' && method === 'PUT') {
|
|
2366
2805
|
try {
|
|
2367
2806
|
const parsed = await readJsonBody(req);
|
|
2368
2807
|
const updates = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)
|
|
@@ -2380,20 +2819,20 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2380
2819
|
}
|
|
2381
2820
|
return true;
|
|
2382
2821
|
}
|
|
2383
|
-
if (pathname === '/api/
|
|
2822
|
+
if (pathname === '/api/computers/{computer_id}/hub-snapshot' && method === 'GET') {
|
|
2384
2823
|
const spaceId = (url.searchParams.get('space_id') || process.env.SUPEN_SPACE_ID || 'local').trim() || 'local';
|
|
2385
2824
|
writeJson(res, 200, buildHubSnapshotForSpace(spaceId));
|
|
2386
2825
|
return true;
|
|
2387
2826
|
}
|
|
2388
|
-
if (pathname === '/api/
|
|
2827
|
+
if (pathname === '/api/computers/{computer_id}/usage' && method === 'GET') {
|
|
2389
2828
|
writeJson(res, 200, getGlobalUsage());
|
|
2390
2829
|
return true;
|
|
2391
2830
|
}
|
|
2392
|
-
if (pathname === '/api/
|
|
2831
|
+
if (pathname === '/api/computers/{computer_id}/usage/daily' && method === 'GET') {
|
|
2393
2832
|
writeJson(res, 200, { daily: getDailyUsage() });
|
|
2394
2833
|
return true;
|
|
2395
2834
|
}
|
|
2396
|
-
if (pathname === '/api/
|
|
2835
|
+
if (pathname === '/api/computers/{computer_id}/codex/subscription' && method === 'GET') {
|
|
2397
2836
|
try {
|
|
2398
2837
|
writeJson(res, 200, await readCodexSubscription());
|
|
2399
2838
|
}
|
|
@@ -2402,17 +2841,45 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2402
2841
|
}
|
|
2403
2842
|
return true;
|
|
2404
2843
|
}
|
|
2405
|
-
if (pathname === '/api/
|
|
2844
|
+
if (pathname === '/api/computers/{computer_id}/quota-status' && method === 'GET') {
|
|
2406
2845
|
writeJson(res, 200, readLatestSpaceQuotaStatus());
|
|
2407
2846
|
return true;
|
|
2408
2847
|
}
|
|
2409
|
-
if (pathname === '/api/
|
|
2848
|
+
if (pathname === '/api/computers/{computer_id}/codex/threads' && method === 'GET') {
|
|
2410
2849
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
2411
2850
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : MIRRORED_THREAD_LIMIT;
|
|
2412
2851
|
writeJson(res, 200, readMirroredTaskProjects(Number.isFinite(limit) ? limit : MIRRORED_THREAD_LIMIT));
|
|
2413
2852
|
return true;
|
|
2414
2853
|
}
|
|
2415
|
-
|
|
2854
|
+
if (pathname === '/api/computers/{computer_id}/files/preview' && (method === 'GET' || method === 'HEAD')) {
|
|
2855
|
+
const requestedPath = coerceSingleQueryParam(url.searchParams.get('path')) || '';
|
|
2856
|
+
const filePath = resolveRemoteFilePath(requestedPath);
|
|
2857
|
+
if (!filePath) {
|
|
2858
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_file_path', 'A valid file path is required.');
|
|
2859
|
+
return true;
|
|
2860
|
+
}
|
|
2861
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
2862
|
+
writeProtocolError(res, 404, 'not_found', 'remote_file_not_found', 'File not found.');
|
|
2863
|
+
return true;
|
|
2864
|
+
}
|
|
2865
|
+
serveRemoteFile(req, res, filePath);
|
|
2866
|
+
return true;
|
|
2867
|
+
}
|
|
2868
|
+
const mirroredThreadArchiveMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/archive$/);
|
|
2869
|
+
if (mirroredThreadArchiveMatch && method === 'POST') {
|
|
2870
|
+
const threadId = decodeURIComponent(mirroredThreadArchiveMatch[1] || '').trim();
|
|
2871
|
+
if (!threadId) {
|
|
2872
|
+
writeProtocolError(res, 400, 'validation_error', 'missing_thread_id', 'Thread id is required.');
|
|
2873
|
+
return true;
|
|
2874
|
+
}
|
|
2875
|
+
if (!archiveMirroredThread(threadId)) {
|
|
2876
|
+
writeProtocolError(res, 404, 'not_found', 'mirrored_thread_not_found', 'Mirrored task was not found.');
|
|
2877
|
+
return true;
|
|
2878
|
+
}
|
|
2879
|
+
writeJson(res, 200, { ok: true, archived: true });
|
|
2880
|
+
return true;
|
|
2881
|
+
}
|
|
2882
|
+
const mirroredThreadStreamMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/stream$/);
|
|
2416
2883
|
if (mirroredThreadStreamMatch && method === 'GET') {
|
|
2417
2884
|
const threadId = decodeURIComponent(mirroredThreadStreamMatch[1] || '').trim();
|
|
2418
2885
|
if (!threadId) {
|
|
@@ -2455,7 +2922,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2455
2922
|
res.flush?.();
|
|
2456
2923
|
return true;
|
|
2457
2924
|
}
|
|
2458
|
-
const mirroredThreadHistoryMatch = pathname.match(/^\/api\/
|
|
2925
|
+
const mirroredThreadHistoryMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/messages$/);
|
|
2459
2926
|
if (mirroredThreadHistoryMatch && method === 'GET') {
|
|
2460
2927
|
const threadId = decodeURIComponent(mirroredThreadHistoryMatch[1] || '').trim();
|
|
2461
2928
|
const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
|
|
@@ -2470,7 +2937,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2470
2937
|
writeJson(res, 200, history);
|
|
2471
2938
|
return true;
|
|
2472
2939
|
}
|
|
2473
|
-
const mirroredThreadAdoptMatch = pathname.match(/^\/api\/
|
|
2940
|
+
const mirroredThreadAdoptMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/codex\/threads\/([^/]+)\/adopt$/);
|
|
2474
2941
|
if (mirroredThreadAdoptMatch && method === 'POST') {
|
|
2475
2942
|
const threadId = decodeURIComponent(mirroredThreadAdoptMatch[1] || '').trim();
|
|
2476
2943
|
const parsed = await readJsonBody(req);
|
|
@@ -2487,7 +2954,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2487
2954
|
writeJson(res, 200, { thread: serializeAdoptedMirroredThread(session) });
|
|
2488
2955
|
return true;
|
|
2489
2956
|
}
|
|
2490
|
-
if (pathname === '/api/
|
|
2957
|
+
if (pathname === '/api/computers/{computer_id}/codex/projects/open' && method === 'POST') {
|
|
2491
2958
|
try {
|
|
2492
2959
|
const parsed = await readJsonBody(req);
|
|
2493
2960
|
const projectPath = parsed && typeof parsed === 'object' && typeof parsed.path === 'string'
|
|
@@ -2514,12 +2981,12 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2514
2981
|
}
|
|
2515
2982
|
return true;
|
|
2516
2983
|
}
|
|
2517
|
-
if (pathname === '/api/
|
|
2984
|
+
if (pathname === '/api/computers/{computer_id}/mcp/servers' && method === 'GET') {
|
|
2518
2985
|
writeJson(res, 200, { servers: buildMcpSettingsResponse().servers });
|
|
2519
2986
|
return true;
|
|
2520
2987
|
}
|
|
2521
2988
|
/** Tools discovered via MCP `tools/list` for each connected server (in-memory; empty if servers not connected). */
|
|
2522
|
-
if (pathname === '/api/
|
|
2989
|
+
if (pathname === '/api/computers/{computer_id}/mcp/tool-catalog' && method === 'GET') {
|
|
2523
2990
|
try {
|
|
2524
2991
|
const force = url.searchParams.get('refresh') === '1' || url.searchParams.get('force') === '1';
|
|
2525
2992
|
const mgr = await getMcpManager({ forceConfigRefresh: force });
|
|
@@ -2538,11 +3005,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2538
3005
|
}
|
|
2539
3006
|
return true;
|
|
2540
3007
|
}
|
|
2541
|
-
if (pathname === '/api/
|
|
3008
|
+
if (pathname === '/api/computers/{computer_id}/mcp/settings' && method === 'GET') {
|
|
2542
3009
|
writeJson(res, 200, buildMcpSettingsResponse());
|
|
2543
3010
|
return true;
|
|
2544
3011
|
}
|
|
2545
|
-
if (pathname === '/api/
|
|
3012
|
+
if (pathname === '/api/computers/{computer_id}/mcp/settings' && method === 'PUT') {
|
|
2546
3013
|
const parsed = await readJsonBody(req);
|
|
2547
3014
|
const allowedKeys = new Set(listMcpEnvKeys());
|
|
2548
3015
|
const payloadEnv = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object'
|
|
@@ -2563,25 +3030,14 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2563
3030
|
writeJson(res, 200, buildMcpSettingsResponse());
|
|
2564
3031
|
return true;
|
|
2565
3032
|
}
|
|
2566
|
-
if (pathname === '/api/
|
|
2567
|
-
const cpus = os.cpus();
|
|
2568
|
-
const memTotal = os.totalmem();
|
|
2569
|
-
const memFree = os.freemem();
|
|
3033
|
+
if (pathname === '/api/computers/{computer_id}' && method === 'GET') {
|
|
2570
3034
|
const enroll = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
2571
|
-
|
|
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 */ }
|
|
3035
|
+
const daemon = detectDaemonPackageStatus();
|
|
2581
3036
|
writeJson(res, 200, {
|
|
2582
3037
|
daemon_id: enroll.daemon_id,
|
|
2583
3038
|
hostname: os.hostname(),
|
|
2584
|
-
version:
|
|
3039
|
+
version: daemon.version,
|
|
3040
|
+
daemon,
|
|
2585
3041
|
runtime: {
|
|
2586
3042
|
node: process.version,
|
|
2587
3043
|
platform: process.platform,
|
|
@@ -2602,31 +3058,17 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2602
3058
|
queued_tasks: 0,
|
|
2603
3059
|
total_slots: 0,
|
|
2604
3060
|
},
|
|
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
|
-
},
|
|
3061
|
+
os: collectOsTelemetryFields(),
|
|
2620
3062
|
});
|
|
2621
3063
|
return true;
|
|
2622
3064
|
}
|
|
2623
3065
|
// ── Enrollment ──
|
|
2624
|
-
if (pathname === '/api/
|
|
3066
|
+
if (pathname === '/api/computers/{computer_id}/enroll/status' && method === 'GET') {
|
|
2625
3067
|
const state = readEnrollmentState(DAEMON_HOSTNAME || undefined);
|
|
2626
3068
|
writeJson(res, 200, state);
|
|
2627
3069
|
return true;
|
|
2628
3070
|
}
|
|
2629
|
-
if (pathname === '/api/
|
|
3071
|
+
if (pathname === '/api/computers/{computer_id}/enroll/verify' && method === 'POST') {
|
|
2630
3072
|
const parsed = await readJsonBody(req);
|
|
2631
3073
|
const result = verifyEnrollmentToken({
|
|
2632
3074
|
token: parsed.token,
|
|
@@ -2636,12 +3078,12 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2636
3078
|
writeJson(res, result.ok ? 200 : 401, result);
|
|
2637
3079
|
return true;
|
|
2638
3080
|
}
|
|
2639
|
-
if (pathname === '/api/
|
|
2640
|
-
writeProtocolError(res, 410, 'auth_error', 'trust_endpoint_removed', 'Direct trust elevation is disabled. Use the enrollment flow (/api/
|
|
3081
|
+
if (pathname === '/api/computers/{computer_id}/trust' && method === 'POST') {
|
|
3082
|
+
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
3083
|
return true;
|
|
2642
3084
|
}
|
|
2643
3085
|
// Create enrollment token
|
|
2644
|
-
if (pathname === '/api/
|
|
3086
|
+
if (pathname === '/api/computers/{computer_id}/enroll/token' && method === 'POST') {
|
|
2645
3087
|
try {
|
|
2646
3088
|
const parsed = await readJsonBody(req);
|
|
2647
3089
|
const ttlMinutes = parsed?.ttl_minutes || DEFAULT_TOKEN_TTL_MINUTES;
|
|
@@ -2661,7 +3103,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2661
3103
|
return true;
|
|
2662
3104
|
}
|
|
2663
3105
|
// Revoke enrollment
|
|
2664
|
-
if (pathname === '/api/
|
|
3106
|
+
if (pathname === '/api/computers/{computer_id}/enroll/revoke' && method === 'POST') {
|
|
2665
3107
|
try {
|
|
2666
3108
|
setTrustState('revoked', { daemonId: DAEMON_HOSTNAME || undefined });
|
|
2667
3109
|
writeJson(res, 200, { ok: true, trust_state: 'revoked' });
|