@walkhi/code-relax 0.1.0-beta.26 → 0.1.0-beta.28
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 +80 -67
- package/dist/bin/codex-remote.mjs +286 -239
- package/dist/shared/app-server-events.cjs +102 -16
- package/dist/shared/code-browser.cjs +159 -0
- package/dist/shared/file-changes.cjs +31 -0
- package/dist/shared/file-citations.cjs +117 -0
- package/dist/shared/file-mentions.cjs +80 -35
- package/dist/shared/message-delivery.cjs +38 -0
- package/dist/shared/p2p-file-channel.cjs +74 -0
- package/dist/shared/server-requests.cjs +149 -41
- package/dist/shared/service-endpoints.cjs +11 -11
- package/dist/shared/timeline-sync.cjs +173 -156
- package/dist/skills/code-relax/SKILL.md +2 -1
- package/dist/src/bounded-process-log.mjs +63 -0
- package/dist/src/celsius-client.mjs +107 -0
- package/dist/src/code-browser.mjs +101 -0
- package/dist/src/desktop-launcher.mjs +30 -30
- package/dist/src/dots-approval-timeline.mjs +99 -0
- package/dist/src/dots-avatar.mjs +42 -0
- package/dist/src/dots-chat-projection.mjs +50 -0
- package/dist/src/dots-chat-server.mjs +412 -0
- package/dist/src/dots-message-images.mjs +319 -0
- package/dist/src/dots-profile.mjs +77 -0
- package/dist/src/dots-room-activity.mjs +94 -0
- package/dist/src/dots-room-client.mjs +135 -0
- package/dist/src/dots-room-projection.mjs +94 -0
- package/dist/src/dots-schedules.mjs +66 -0
- package/dist/src/dots-summary-tasks.mjs +32 -0
- package/dist/src/dots-task-catalog.mjs +411 -0
- package/dist/src/file-transfer-range.mjs +22 -0
- package/dist/src/file-upload-session.mjs +130 -124
- package/dist/src/hosted-history-app-server.mjs +103 -0
- package/dist/src/hosted-history-cache.mjs +113 -0
- package/dist/src/lan-socket-server.mjs +205 -160
- package/dist/src/managed-app-server.mjs +241 -238
- package/dist/src/message-upload-files.mjs +74 -0
- package/dist/src/platform/windows/activate-desktop.ps1 +48 -0
- package/dist/src/platform/windows/codex-runtime.mjs +253 -251
- package/dist/src/platform/windows/desktop-host.mjs +38 -32
- package/dist/src/platform/windows/desktop-info.ps1 +2 -2
- package/dist/src/platform/windows/desktop-project-registration.mjs +153 -0
- package/dist/src/platform/windows/dots-profile.mjs +49 -0
- package/dist/src/platform/windows/launch-worker.mjs +41 -42
- package/dist/src/platform/windows/project-locations.mjs +16 -0
- package/dist/src/project-create.mjs +119 -0
- package/dist/src/project-management.mjs +69 -0
- package/dist/src/self-relay/account-connections.mjs +52 -0
- package/dist/src/self-relay/admin-page.mjs +80 -80
- package/dist/src/self-relay/admin-style.mjs +14 -9
- package/dist/src/self-relay/connector.mjs +22 -4
- package/dist/src/self-relay/file-wire.mjs +13 -0
- package/dist/src/self-relay/p2p-probe.mjs +152 -123
- package/dist/src/self-relay/rtc-file-bridge.mjs +41 -0
- package/dist/src/self-relay/server.mjs +894 -849
- package/dist/src/server.mjs +463 -148
- package/dist/src/service-doctor.mjs +72 -61
- package/dist/src/service-lifecycle.mjs +51 -43
- package/dist/src/shared-app-server.mjs +166 -107
- package/dist/src/shared-catalog.mjs +132 -40
- package/dist/src/shared-thread-stream.mjs +103 -25
- package/dist/src/usage-reset.mjs +17 -0
- package/dist/src/workstation-downloads.mjs +120 -0
- package/package.json +1 -1
|
@@ -1,61 +1,72 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { readRecord } from './service-lifecycle.mjs';
|
|
4
|
-
import { inspectSkill } from './skill-install.mjs';
|
|
5
|
-
import { recoveryStatus } from './shared-recovery.mjs';
|
|
6
|
-
|
|
7
|
-
export async function doctor({ paths, host, service, skillTarget, ensureAutostart }) {
|
|
8
|
-
const checks = [];
|
|
9
|
-
let installedVersion;
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!record
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (state.managedAppServer
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readRecord } from './service-lifecycle.mjs';
|
|
4
|
+
import { inspectSkill } from './skill-install.mjs';
|
|
5
|
+
import { recoveryStatus } from './shared-recovery.mjs';
|
|
6
|
+
|
|
7
|
+
export async function doctor({ paths, host, service, skillTarget, ensureAutostart }) {
|
|
8
|
+
const checks = [];
|
|
9
|
+
let installedVersion;
|
|
10
|
+
let kernelUpdate = null;
|
|
11
|
+
const add = (id, status, message) => checks.push({ id, status, message });
|
|
12
|
+
const check = async (id, task) => {
|
|
13
|
+
try { await task(); } catch (error) { add(id, 'error', error.message); }
|
|
14
|
+
};
|
|
15
|
+
await check('installation', async () => {
|
|
16
|
+
const record = readRecord(path.join(paths.serviceRoot, 'installation.json'));
|
|
17
|
+
if (!record || !fs.existsSync(path.join(paths.serviceRoot, 'dist/bin/codex-remote.mjs'))) throw Error('尚未完整部署服务,请重新安装 Code Relax。');
|
|
18
|
+
if (!record.nodeExecutable || !fs.existsSync(record.nodeExecutable)) throw Error('记录的 Node 已失效,请用可用的 Node 重新安装 Code Relax。');
|
|
19
|
+
installedVersion = record.version;
|
|
20
|
+
add('installation', 'ok', `已安装版本:${record.version};Node:${record.nodeExecutable}`);
|
|
21
|
+
});
|
|
22
|
+
await check('bridge-runtime', async () => {
|
|
23
|
+
await host.bridgeEnvironment();
|
|
24
|
+
add('bridge-runtime', 'ok', '桌面服务启动环境已就绪;实际任务操作尚未验证。');
|
|
25
|
+
});
|
|
26
|
+
await check('network', async () => {
|
|
27
|
+
const address = await host.lanAddress();
|
|
28
|
+
if (!address) throw Error('未找到局域网 IPv4;请连接受信任的 Wi-Fi 或有线网络。');
|
|
29
|
+
add('network', 'ok', `局域网地址:${address}`);
|
|
30
|
+
});
|
|
31
|
+
await check('autostart', async () => {
|
|
32
|
+
const state = await ensureAutostart();
|
|
33
|
+
add('autostart', 'ok', `当前用户登录自启:${state.repaired ? '已自动恢复' : '已启用'}。`);
|
|
34
|
+
});
|
|
35
|
+
await check('service', async () => {
|
|
36
|
+
const state = await service.status();
|
|
37
|
+
add('service', state.running ? 'ok' : 'warning', state.running
|
|
38
|
+
? `本机服务正常:${state.mode},端口 ${state.port},运行版本:${state.version || '未知'}` : '服务未就绪;安装后执行 relax start。');
|
|
39
|
+
if (state.running && installedVersion && state.version !== installedVersion) add('service-version', 'warning', state.version
|
|
40
|
+
? `已安装版本 ${installedVersion},但当前运行版本为 ${state.version};请重新执行安装或重启工作站服务。`
|
|
41
|
+
: `已安装版本 ${installedVersion},但当前运行的桌面服务未报告版本,可能仍是旧进程;请重新执行安装或重启桌面服务。`);
|
|
42
|
+
const currentVersion = state.managedAppServer?.version || null;
|
|
43
|
+
const selectedVersion = state.managedAppServer?.selectedVersion || null;
|
|
44
|
+
if (state.managedAppServer) kernelUpdate = { pending: state.managedAppServer.updatePending === true,
|
|
45
|
+
currentVersion, selectedVersion,
|
|
46
|
+
command: state.managedAppServer.updatePending ? 'relax kernel-restart --confirm' : null };
|
|
47
|
+
if (state.running) add('managed-app-server', state.managedAppServer?.state === 'ready' ? 'ok' : 'error',
|
|
48
|
+
state.managedAppServer?.state === 'ready' ? `Codex 内核已就绪:版本 ${currentVersion || '未知'}。`
|
|
49
|
+
: state.managedAppServer?.error || 'Codex 内核尚未就绪。');
|
|
50
|
+
if (state.managedAppServer?.updatePending) {
|
|
51
|
+
const versionDetail = currentVersion && selectedVersion && currentVersion === selectedVersion
|
|
52
|
+
? `内核版本号未变(${currentVersion}),重启后将使用更新后的安装文件`
|
|
53
|
+
: `当前内核 ${currentVersion || '未知'},待应用 ${selectedVersion || '未知'}`;
|
|
54
|
+
add('managed-app-server-update', 'warning',
|
|
55
|
+
`Codex 更新待应用:${versionDetail};执行 relax kernel-restart --confirm 应用更新。`);
|
|
56
|
+
}
|
|
57
|
+
if (state.managedAppServer?.launcherRestartRequired) add('managed-app-server-launcher', 'warning',
|
|
58
|
+
'安装文件已经更新,但当前 app-server 仍由旧启动链创建;隐藏控制台等运行时修复尚未生效,请执行 relax setup 或重启 Windows。');
|
|
59
|
+
});
|
|
60
|
+
await check('desktop-recovery', async () => {
|
|
61
|
+
const state = await recoveryStatus(paths.stateRoot);
|
|
62
|
+
add('desktop-recovery', state.status === 'failed' ? 'warning' : 'ok',
|
|
63
|
+
`最近 Desktop 接入:${state.message || state.status}${state.error ? `;${state.error}` : ''}`);
|
|
64
|
+
});
|
|
65
|
+
await check('skill', async () => {
|
|
66
|
+
const state = inspectSkill(skillTarget);
|
|
67
|
+
add('skill', state.managed ? 'ok' : 'warning', state.managed ? '本包管理的 Skill 已安装。'
|
|
68
|
+
: state.installed ? '存在同名自定义 Skill,本包不会覆盖。' : 'Skill 未安装;重新执行 relax setup。');
|
|
69
|
+
});
|
|
70
|
+
add('firewall', 'warning', '移动端连通性未验证。首次启动请允许记录的 Node 在受信任的专用网络通信;若曾拒绝,可在 Windows 安全中心的防火墙“允许应用通过防火墙”中检查该 Node。不要关闭整个防火墙。');
|
|
71
|
+
return { ready: !checks.some(item => item.status === 'error'), lanAccess: 'unverified', kernelUpdate, checks };
|
|
72
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { startBackground, processAlive } from './background-process.mjs';
|
|
3
|
+
import { startBackground, processAlive } from './background-process.mjs';
|
|
4
4
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
5
5
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -27,7 +27,7 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
27
27
|
|
|
28
28
|
async function alive(session) {
|
|
29
29
|
if (!session?.controlPort || !session.instanceId || !session.controlSecret) {
|
|
30
|
-
if (await health(session)) throw new Error('当前为旧版桌面服务,请先使用旧版停止入口结束服务,再启动新版。');
|
|
30
|
+
if (await health(session)) throw new Error('当前为旧版桌面服务,请先使用旧版停止入口结束服务,再启动新版。');
|
|
31
31
|
return false;
|
|
32
32
|
}
|
|
33
33
|
try { await controlRequest(session); return true; }
|
|
@@ -35,7 +35,7 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
35
35
|
}
|
|
36
36
|
async function stop() {
|
|
37
37
|
const session = readRecord(sessionPath);
|
|
38
|
-
if (!await alive(session)) return { stopped: true, message: '桌面服务已停止。' };
|
|
38
|
+
if (!await alive(session)) return { stopped: true, message: '桌面服务已停止。' };
|
|
39
39
|
await controlRequest(session, 'POST');
|
|
40
40
|
const deadline = Date.now() + 10000;
|
|
41
41
|
while (true) {
|
|
@@ -45,50 +45,51 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
45
45
|
// Verify refusal on the next connection; never resend the shutdown request.
|
|
46
46
|
if (error.code !== 'ECONNRESET') throw error;
|
|
47
47
|
}
|
|
48
|
-
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内退出;未强杀进程。');
|
|
48
|
+
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内退出;未强杀进程。');
|
|
49
49
|
await delay(100);
|
|
50
50
|
}
|
|
51
|
-
return { stopped: true, message: '桌面服务已正常关闭。' };
|
|
51
|
+
return { stopped: true, message: '桌面服务已正常关闭。' };
|
|
52
52
|
}
|
|
53
53
|
async function status() {
|
|
54
54
|
const session = readRecord(sessionPath);
|
|
55
55
|
const running = await alive(session);
|
|
56
|
-
const result = running ? await health(session) : null;
|
|
57
|
-
return { running: running && result?.status === 'ok', processRunning: running, pid: session?.pid,
|
|
58
|
-
port: session?.port, mode: result?.transportMode, version: result?.version, execution: result?.execution,
|
|
59
|
-
managedAppServer: result?.managedAppServer, desktop: result?.desktop,
|
|
60
|
-
desktopRestartRecommended: result?.desktopRestartRecommended === true,
|
|
61
|
-
resident: result?.resident === true };
|
|
56
|
+
const result = running ? await health(session) : null;
|
|
57
|
+
return { running: running && result?.status === 'ok', processRunning: running, pid: session?.pid,
|
|
58
|
+
port: session?.port, mode: result?.transportMode, version: result?.version, execution: result?.execution,
|
|
59
|
+
managedAppServer: result?.managedAppServer, desktop: result?.desktop,
|
|
60
|
+
desktopRestartRecommended: result?.desktopRestartRecommended === true,
|
|
61
|
+
resident: result?.resident === true };
|
|
62
62
|
}
|
|
63
|
-
async function pair() {
|
|
63
|
+
async function pair() {
|
|
64
64
|
const session = readRecord(sessionPath);
|
|
65
65
|
const connection = await alive(session) ? await health(session) : null;
|
|
66
66
|
if (connection?.status !== 'ok' || !session.lan) {
|
|
67
|
-
throw new Error('没有健康的局域网服务,请先执行 relax start。');
|
|
67
|
+
throw new Error('没有健康的局域网服务,请先执行 relax start。');
|
|
68
68
|
}
|
|
69
69
|
const address = await host.lanAddress();
|
|
70
70
|
const url = new URL(session.accessUrl);
|
|
71
|
-
url.hostname = address || '127.0.0.1';
|
|
71
|
+
url.hostname = address || '127.0.0.1';
|
|
72
72
|
session.accessUrl = url.href;
|
|
73
73
|
writeRecord(sessionPath, session);
|
|
74
|
-
return { pid: session.pid, port: session.port, transportMode: connection.transportMode,
|
|
75
|
-
execution: connection.execution, managedAppServer: connection.managedAppServer, desktop: connection.desktop,
|
|
76
|
-
desktopRestartRecommended: connection.desktopRestartRecommended === true, accessUrl: session.accessUrl,
|
|
77
|
-
qrPath: path.join(stateRoot, 'connection-qr.png') };
|
|
78
|
-
}
|
|
79
|
-
async function restartManagedAppServer() {
|
|
80
|
-
const session = readRecord(sessionPath);
|
|
81
|
-
if (!await alive(session)) throw new Error('桌面服务尚未运行,无法迁移 Codex 内核。');
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
return { pid: session.pid, port: session.port, transportMode: connection.transportMode,
|
|
75
|
+
execution: connection.execution, managedAppServer: connection.managedAppServer, desktop: connection.desktop,
|
|
76
|
+
desktopRestartRecommended: connection.desktopRestartRecommended === true, accessUrl: session.accessUrl,
|
|
77
|
+
qrPath: path.join(stateRoot, 'connection-qr.png') };
|
|
78
|
+
}
|
|
79
|
+
async function restartManagedAppServer(confirmed = false) {
|
|
80
|
+
const session = readRecord(sessionPath);
|
|
81
|
+
if (!await alive(session)) throw new Error('桌面服务尚未运行,无法迁移 Codex 内核。');
|
|
82
|
+
const action = confirmed ? 'restart-managed-app-server-confirmed' : 'restart-managed-app-server';
|
|
83
|
+
return controlRequest(session, 'POST', action);
|
|
84
|
+
}
|
|
85
|
+
async function startBridge() {
|
|
86
|
+
let session = readRecord(sessionPath);
|
|
87
|
+
if (await alive(session)) {
|
|
87
88
|
const result = await health(session);
|
|
88
|
-
if (session.scriptPath === scriptPath && session.lan && result?.status === 'ok' && result.resident) {
|
|
89
|
-
await controlRequest(session, 'POST', 'refresh-execution');
|
|
90
|
-
return pair();
|
|
91
|
-
}
|
|
89
|
+
if (session.scriptPath === scriptPath && session.lan && result?.status === 'ok' && result.resident) {
|
|
90
|
+
await controlRequest(session, 'POST', 'refresh-execution');
|
|
91
|
+
return pair();
|
|
92
|
+
}
|
|
92
93
|
await stop();
|
|
93
94
|
}
|
|
94
95
|
let config = readRecord(configPath);
|
|
@@ -97,24 +98,24 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
97
98
|
if (!Number.isInteger(config.port) || config.port < 1 || config.port > 65535 || !/^[A-Za-z0-9_-]{20,}$/.test(config.token)) {
|
|
98
99
|
throw new Error('bridge-config.json 中端口或 Token 无效;不会自动重置凭据。');
|
|
99
100
|
}
|
|
100
|
-
const address = await host.lanAddress() || '127.0.0.1';
|
|
101
|
-
const { executable, environment } = await host.bridgeEnvironment();
|
|
102
|
-
writeRecord(configPath, config);
|
|
101
|
+
const address = await host.lanAddress() || '127.0.0.1';
|
|
102
|
+
const { executable, environment } = await host.bridgeEnvironment();
|
|
103
|
+
writeRecord(configPath, config);
|
|
103
104
|
const instanceId = randomUUID();
|
|
104
105
|
const controlSecret = randomBytes(32).toString('base64url');
|
|
105
106
|
const readyPath = path.join(stateRoot, `bridge-ready-${instanceId}.json`);
|
|
106
|
-
const pid = await startBackground(executable, [scriptPath, '--lan', '--port', String(config.port), '--token', config.token],
|
|
107
|
-
{ ...environment, CODEX_REMOTE_INSTANCE: instanceId, CODEX_REMOTE_CONTROL_SECRET: controlSecret,
|
|
108
|
-
CODEX_REMOTE_READY_PATH: readyPath }, [path.join(stateRoot, 'bridge.stdout.log'), path.join(stateRoot, 'bridge.stderr.log')]);
|
|
107
|
+
const pid = await startBackground(executable, [scriptPath, '--lan', '--port', String(config.port), '--token', config.token],
|
|
108
|
+
{ ...environment, CODEX_REMOTE_INSTANCE: instanceId, CODEX_REMOTE_CONTROL_SECRET: controlSecret,
|
|
109
|
+
CODEX_REMOTE_READY_PATH: readyPath }, [path.join(stateRoot, 'bridge.stdout.log'), path.join(stateRoot, 'bridge.stderr.log')]);
|
|
109
110
|
try {
|
|
110
111
|
const deadline = Date.now() + 10000;
|
|
111
112
|
let ready;
|
|
112
113
|
while (!(ready = readRecord(readyPath))) {
|
|
113
|
-
if (!processAlive(pid)) throw new Error('桌面服务启动失败,请查看 bridge.stderr.log。');
|
|
114
|
-
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内就绪。');
|
|
114
|
+
if (!processAlive(pid)) throw new Error('桌面服务启动失败,请查看 bridge.stderr.log。');
|
|
115
|
+
if (Date.now() > deadline) throw new Error('桌面服务未在 10 秒内就绪。');
|
|
115
116
|
await delay(100);
|
|
116
117
|
}
|
|
117
|
-
if (ready.instanceId !== instanceId || ready.pid !== pid) throw new Error('桌面服务启动实例不匹配。');
|
|
118
|
+
if (ready.instanceId !== instanceId || ready.pid !== pid) throw new Error('桌面服务启动实例不匹配。');
|
|
118
119
|
session = { ...ready, controlSecret, executablePath: executable, scriptPath, port: config.port,
|
|
119
120
|
lan: true, appServer: false, accessUrl: `http://${address}:${config.port}/?token=${config.token}`,
|
|
120
121
|
startedAt: new Date().toISOString() };
|
|
@@ -122,9 +123,16 @@ export function lifecycle({ stateRoot, host }) {
|
|
|
122
123
|
writeRecord(sessionPath, session);
|
|
123
124
|
return await pair();
|
|
124
125
|
} catch (error) {
|
|
125
|
-
if (processAlive(pid)) process.kill(pid);
|
|
126
|
+
if (processAlive(pid)) process.kill(pid);
|
|
126
127
|
throw error;
|
|
127
128
|
} finally { fs.rmSync(readyPath, { force: true }); }
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
async function start() {
|
|
131
|
+
const result = await startBridge();
|
|
132
|
+
if (fs.existsSync(path.join(stateRoot, 'self-relay-config.json'))) {
|
|
133
|
+
await controlRequest(readRecord(sessionPath), 'POST', 'relay-start');
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
return { start, pair, status, stop, restartManagedAppServer };
|
|
138
|
+
}
|