@xuda.io/ai_module 1.1.5660 → 1.1.5661
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/index.mjs +57 -4
- package/index_ms.mjs +4 -0
- package/index_msa.mjs +4 -0
- package/package.json +1 -1
- package/xucode_preview.mjs +38 -0
- package/xucode_vm.mjs +7 -3
package/index.mjs
CHANGED
|
@@ -141,7 +141,7 @@ const xucode_verify = await import('./xucode_verify.mjs');
|
|
|
141
141
|
// Where the provider credential lives. The machine gets a run token; this box keeps the key.
|
|
142
142
|
const xucode_proxy = await import('./xucode_proxy.mjs');
|
|
143
143
|
// The last gate of that loop: is the app still actually running. See xucode_preview.mjs.
|
|
144
|
-
const { create_preview: _create_xucode_preview } = await import('./xucode_preview.mjs');
|
|
144
|
+
const { create_preview: _create_xucode_preview, preview_host: xucode_preview_host } = await import('./xucode_preview.mjs');
|
|
145
145
|
const xucode_preview = _create_xucode_preview({});
|
|
146
146
|
const xucode_runtime = _create_xucode_runtime({ run_process });
|
|
147
147
|
|
|
@@ -3904,6 +3904,54 @@ const xucode_prepare_engine = async function ({ uid, profile_id, engine, model,
|
|
|
3904
3904
|
return { code: 1, launch, has_key: !!api_key, proxied: false, byok: key_ref.has_key };
|
|
3905
3905
|
};
|
|
3906
3906
|
|
|
3907
|
+
// What the Code tab shows for a project's preview (plan 7).
|
|
3908
|
+
//
|
|
3909
|
+
// Deliberately does NOT wake a sleeping machine. A panel that polls would otherwise keep every
|
|
3910
|
+
// machine awake forever, which is the opposite of what sleep is for and would bill the customer
|
|
3911
|
+
// for a screen they left open. So a sleeping machine reports 'asleep' with its URL, and the next
|
|
3912
|
+
// run wakes it in the normal way.
|
|
3913
|
+
//
|
|
3914
|
+
// The URL is derived rather than stored, so it is always the one the router will resolve; there is
|
|
3915
|
+
// no second copy to drift. `listening` is the only part that needs the machine, and it is asked
|
|
3916
|
+
// for only when the machine is already up.
|
|
3917
|
+
export const xucode_preview_status = async function (req) {
|
|
3918
|
+
const { uid, profile_id, app_id } = req || {};
|
|
3919
|
+
try {
|
|
3920
|
+
if (!app_id) return { code: -1, data: 'app_id is required' };
|
|
3921
|
+
const host = xucode_preview_host(app_id);
|
|
3922
|
+
const port = xucode_preview.preview_port(app_id);
|
|
3923
|
+
const base = { url: host ? `https://${host}/` : null, port };
|
|
3924
|
+
|
|
3925
|
+
// `no_wake`: a status read must never resume a machine, or a panel left open keeps every
|
|
3926
|
+
// machine awake and bills the customer for a screen nobody is watching.
|
|
3927
|
+
const machine = await deploy_ms.xucode_machine_for({ uid, app_id, no_wake: true }).catch(() => null);
|
|
3928
|
+
if (!machine || machine.code < 0) {
|
|
3929
|
+
// Asleep, quarantined and "no machine at all" are three different answers, and the customer
|
|
3930
|
+
// deserves the right one: only the middle is a fault.
|
|
3931
|
+
const state = machine?.asleep ? 'asleep' : machine?.quarantined ? 'quarantined' : 'unavailable';
|
|
3932
|
+
return { code: 1, data: { ...base, state, why: String(machine?.data || 'this project has no machine yet') } };
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
// The machine is up (xucode_machine_for refuses a quarantined one and wakes nothing that is
|
|
3936
|
+
// merely asleep beyond its own resume path), so it is safe to ask whether anything is serving.
|
|
3937
|
+
const dir = path.join(_conf.xucode?.projects_root || '/srv/xucode', String(app_id));
|
|
3938
|
+
let listening = false;
|
|
3939
|
+
try {
|
|
3940
|
+
const workspace = await xucode_runtime.acquire({ uid, project_id: app_id, app_id, dir });
|
|
3941
|
+
const status = await xucode_preview.status(workspace, app_id, port);
|
|
3942
|
+
listening = status?.listening === true;
|
|
3943
|
+
await workspace.release().catch(() => {});
|
|
3944
|
+
} catch (e) {
|
|
3945
|
+
return { code: 1, data: { ...base, state: 'unavailable', why: 'Xuda could not reach this project just now.' } };
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
return { code: 1, data: { ...base, state: listening ? 'running' : 'stopped' } };
|
|
3949
|
+
} catch (err) {
|
|
3950
|
+
console.error(`[xucode] preview status failed: ${err?.message || err}`);
|
|
3951
|
+
return { code: -1, data: 'could not read the preview' };
|
|
3952
|
+
}
|
|
3953
|
+
};
|
|
3954
|
+
|
|
3907
3955
|
export const xucode_engine_list = async function () {
|
|
3908
3956
|
return { code: 1, data: { engines: xucode_engines.list_engines() } };
|
|
3909
3957
|
};
|
|
@@ -4192,7 +4240,7 @@ export const xucode_run = async function (req) {
|
|
|
4192
4240
|
// them. Only ever reached with `tracker.enforce` on, so the default remains observe.
|
|
4193
4241
|
if (ret.record.enforced === true) {
|
|
4194
4242
|
try {
|
|
4195
|
-
const q = await deploy_msa.xucode_quarantine({ app_id, reasons: ret.record.reasons || [], run_id });
|
|
4243
|
+
const q = await deploy_msa.xucode_quarantine({ app_id, uid, reasons: ret.record.reasons || [], run_id });
|
|
4196
4244
|
if (q?.code < 0) console.error(`[xucode] quarantine failed for ${app_id}: ${q.data}`);
|
|
4197
4245
|
} catch (e) {
|
|
4198
4246
|
console.error(`[xucode] quarantine threw for ${app_id}: ${e?.message || e}`);
|
|
@@ -4204,8 +4252,13 @@ export const xucode_run = async function (req) {
|
|
|
4204
4252
|
// stopped: touching it would reset the idle clock on a machine we want left alone.
|
|
4205
4253
|
if (!ret.killed && ret.record?.enforced !== true) {
|
|
4206
4254
|
try {
|
|
4207
|
-
|
|
4208
|
-
|
|
4255
|
+
// `uid` matters here: `app_id` is the PROJECT, and touch operates on the MACHINE. Passing
|
|
4256
|
+
// the project alone is why this silently did nothing on every run until now.
|
|
4257
|
+
const touched = await deploy_msa.xucode_touch({ app_id, uid });
|
|
4258
|
+
if (touched?.code < 0) console.warn(`[xucode] could not refresh activity for ${app_id}: ${touched.data}`);
|
|
4259
|
+
} catch (e) {
|
|
4260
|
+
console.warn(`[xucode] touch threw for ${app_id}: ${e?.message || e}`);
|
|
4261
|
+
}
|
|
4209
4262
|
}
|
|
4210
4263
|
|
|
4211
4264
|
return ret;
|
package/index_ms.mjs
CHANGED
|
@@ -81,6 +81,10 @@ export const xucode_key_delete = async function (...args) {
|
|
|
81
81
|
return await broker.send_to_queue("xucode_key_delete", ...args);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
+
export const xucode_preview_status = async function (...args) {
|
|
85
|
+
return await broker.send_to_queue("xucode_preview_status", ...args);
|
|
86
|
+
};
|
|
87
|
+
|
|
84
88
|
export const xucode_engine_list = async function (...args) {
|
|
85
89
|
return await broker.send_to_queue("xucode_engine_list", ...args);
|
|
86
90
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -81,6 +81,10 @@ export const xucode_key_delete = function (...args) {
|
|
|
81
81
|
broker.send_to_queue_async("xucode_key_delete", ...args);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
+
export const xucode_preview_status = function (...args) {
|
|
85
|
+
broker.send_to_queue_async("xucode_preview_status", ...args);
|
|
86
|
+
};
|
|
87
|
+
|
|
84
88
|
export const xucode_engine_list = function (...args) {
|
|
85
89
|
broker.send_to_queue_async("xucode_engine_list", ...args);
|
|
86
90
|
};
|
package/package.json
CHANGED
package/xucode_preview.mjs
CHANGED
|
@@ -39,6 +39,44 @@ export const preview_port = function (project_id, base = num('base_port', 4300),
|
|
|
39
39
|
return base + (h % span);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
// The public hostname a project's preview answers on, and the reason it is a TEMPLATE.
|
|
43
|
+
//
|
|
44
|
+
// The obvious scheme is `<project>.preview.xuda.app`, which is what was asked for and what reads
|
|
45
|
+
// best. It does not work today, and the reason is worth writing down so nobody rediscovers it:
|
|
46
|
+
// Cloudflare's certificate for this zone covers `xuda.app` and `*.xuda.app`, and a DNS wildcard
|
|
47
|
+
// matches exactly ONE label. So `<project>.preview.xuda.app` fails the TLS handshake before any
|
|
48
|
+
// routing happens. Verified against the live edge: the single-label form answers, the two-label
|
|
49
|
+
// form dies with a handshake failure. Issuing a second-level wildcard needs Cloudflare's Advanced
|
|
50
|
+
// Certificate Manager, which is a paid per-zone add-on.
|
|
51
|
+
//
|
|
52
|
+
// So the default is a single label that the existing certificate already covers, and the scheme
|
|
53
|
+
// lives in config: the day that certificate exists, this becomes one config edit rather than a
|
|
54
|
+
// change to the router, the runtime and the tests.
|
|
55
|
+
//
|
|
56
|
+
// The project id is sanitized because it becomes a DNS label, and a label may hold only letters,
|
|
57
|
+
// digits and hyphens.
|
|
58
|
+
export const preview_host = function (project_id) {
|
|
59
|
+
const template = preview_conf().host_template;
|
|
60
|
+
if (!template) return null;
|
|
61
|
+
const label = String(project_id || '').replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
|
62
|
+
if (!label) return null;
|
|
63
|
+
return template.replace('{project}', label).toLowerCase();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// The inverse, for the router: given a hostname, which project is this a preview of. Returns null
|
|
67
|
+
// unless the host matches the configured template exactly, so a preview host can never be confused
|
|
68
|
+
// with the vps_web index, which resolves on the first label alone and would otherwise collide.
|
|
69
|
+
export const preview_project_from_host = function (hostname) {
|
|
70
|
+
const template = preview_conf().host_template;
|
|
71
|
+
if (!template || !hostname) return null;
|
|
72
|
+
const host = String(hostname).split(':')[0].trim().toLowerCase();
|
|
73
|
+
const [prefix, suffix] = template.toLowerCase().split('{project}');
|
|
74
|
+
if (!host.startsWith(prefix) || !host.endsWith(suffix)) return null;
|
|
75
|
+
const label = host.slice(prefix.length, host.length - suffix.length);
|
|
76
|
+
// One label only. Without this, `a.b-preview.xuda.app` would resolve as project `a.b`.
|
|
77
|
+
return /^[a-z0-9]+$/.test(label) ? label : null;
|
|
78
|
+
};
|
|
79
|
+
|
|
42
80
|
// What starts this project. `dev` first because that is the one with hot reload, which is the
|
|
43
81
|
// difference between a preview and a screenshot.
|
|
44
82
|
export const detect_dev_command = function (manifest, package_manager = 'npm') {
|
package/xucode_vm.mjs
CHANGED
|
@@ -37,6 +37,11 @@ export const shq = (value) => `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
|
37
37
|
|
|
38
38
|
const vm_conf = () => global._conf.xucode?.vm || {};
|
|
39
39
|
|
|
40
|
+
// The preview hostname scheme lives with the preview code, not here: this file only needs to hand
|
|
41
|
+
// back a URL, and duplicating the template is how the router and the runtime end up disagreeing
|
|
42
|
+
// about what a preview is called.
|
|
43
|
+
const { preview_host: _preview_host } = await import('./xucode_preview.mjs');
|
|
44
|
+
|
|
40
45
|
export const create_vm_substrate = function ({ pve_request, resolve_machine, delay = (ms) => new Promise((r) => setTimeout(r, ms)), now = () => Date.now() }) {
|
|
41
46
|
if (typeof pve_request !== 'function') throw new Error('create_vm_substrate needs pve_request');
|
|
42
47
|
if (typeof resolve_machine !== 'function') throw new Error('create_vm_substrate needs resolve_machine');
|
|
@@ -249,9 +254,8 @@ export const create_vm_substrate = function ({ pve_request, resolve_machine, del
|
|
|
249
254
|
// the one port that is ever reachable, and it goes through the platform's existing web
|
|
250
255
|
// route for the app rather than a second proxy invented here.
|
|
251
256
|
async expose_port(port) {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
return { url: `https://${String(project_id).replace(/[^a-zA-Z0-9]/g, '')}.${base}`, port };
|
|
257
|
+
const host = _preview_host(project_id);
|
|
258
|
+
return host ? { url: `https://${host}/`, port } : null;
|
|
255
259
|
},
|
|
256
260
|
|
|
257
261
|
// The machine persists between runs, which is the whole point of the paid tiers, so
|