@walkhi/code-relax 0.1.0-beta.50 → 0.1.0-beta.51

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.
Files changed (89) hide show
  1. package/README.md +105 -105
  2. package/dist/bin/cleanup-accounts.mjs +63 -63
  3. package/dist/bin/codex-remote.mjs +403 -403
  4. package/dist/bin/publish-android-release.mjs +63 -63
  5. package/dist/bin/release-upload-server.mjs +190 -190
  6. package/dist/build-platform.json +1 -1
  7. package/dist/get-shared-service-url.ps1 +14 -14
  8. package/dist/shared/code-browser.cjs +159 -159
  9. package/dist/shared/file-changes.cjs +31 -31
  10. package/dist/shared/file-citations.cjs +117 -117
  11. package/dist/shared/file-mentions.cjs +92 -92
  12. package/dist/shared/message-delivery.cjs +115 -115
  13. package/dist/shared/p2p-file-channel.cjs +87 -87
  14. package/dist/shared/server-requests.cjs +311 -311
  15. package/dist/shared/timeline-sync.cjs +173 -173
  16. package/dist/skills/code-relax/SKILL.md +22 -22
  17. package/dist/src/bounded-process-log.mjs +63 -63
  18. package/dist/src/celsius-client.mjs +107 -107
  19. package/dist/src/dots-approval-timeline.mjs +99 -99
  20. package/dist/src/dots-avatar.mjs +42 -42
  21. package/dist/src/dots-chat-projection.mjs +50 -50
  22. package/dist/src/dots-chat-server.mjs +483 -483
  23. package/dist/src/dots-message-images.mjs +319 -319
  24. package/dist/src/dots-profile.mjs +77 -77
  25. package/dist/src/dots-room-activity.mjs +172 -172
  26. package/dist/src/dots-room-client.mjs +135 -135
  27. package/dist/src/dots-room-projection.mjs +94 -94
  28. package/dist/src/dots-schedules.mjs +66 -66
  29. package/dist/src/dots-summary-tasks.mjs +32 -32
  30. package/dist/src/dots-task-catalog.mjs +430 -430
  31. package/dist/src/file-transfer-range.mjs +22 -22
  32. package/dist/src/file-upload-session.mjs +130 -130
  33. package/dist/src/hosted-history-app-server.mjs +103 -103
  34. package/dist/src/hosted-history-cache.mjs +113 -113
  35. package/dist/src/lan-socket-server.mjs +266 -266
  36. package/dist/src/managed-app-server.mjs +373 -373
  37. package/dist/src/managed-endpoint.mjs +25 -25
  38. package/dist/src/message-upload-files.mjs +109 -109
  39. package/dist/src/onboarding.mjs +12 -12
  40. package/dist/src/platform/README.md +1 -1
  41. package/dist/src/platform/codex-protocol.mjs +46 -46
  42. package/dist/src/platform/desktop-project-store.mjs +208 -208
  43. package/dist/src/platform/linux/desktop-host.mjs +87 -87
  44. package/dist/src/platform/linux/process.mjs +53 -53
  45. package/dist/src/platform/linux/system.mjs +50 -50
  46. package/dist/src/platform/linux/user-environment.mjs +9 -9
  47. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  48. package/dist/src/platform/macos/desktop-host.mjs +86 -86
  49. package/dist/src/platform/macos/user-environment.mjs +9 -9
  50. package/dist/src/platform/native-package.mjs +58 -58
  51. package/dist/src/platform/windows/activate-desktop.ps1 +49 -49
  52. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  53. package/dist/src/platform/windows/credential-store.mjs +27 -27
  54. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  55. package/dist/src/platform/windows/desktop-host.mjs +65 -65
  56. package/dist/src/platform/windows/desktop-info.ps1 +38 -38
  57. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  58. package/dist/src/platform/windows/desktop-shortcuts.ps1 +14 -14
  59. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  60. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  61. package/dist/src/platform/windows/project-locations.mjs +16 -16
  62. package/dist/src/platform/windows/service-host.mjs +35 -35
  63. package/dist/src/platform/windows/user-environment.mjs +52 -52
  64. package/dist/src/project-management.mjs +58 -58
  65. package/dist/src/runtime-discovery.mjs +47 -47
  66. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  67. package/dist/src/self-relay/admin-page.mjs +80 -80
  68. package/dist/src/self-relay/admin-style.mjs +14 -14
  69. package/dist/src/self-relay/connector.mjs +439 -439
  70. package/dist/src/self-relay/demo.mjs +31 -31
  71. package/dist/src/self-relay/diagnostics.mjs +54 -54
  72. package/dist/src/self-relay/email-accounts.mjs +183 -183
  73. package/dist/src/self-relay/file-wire.mjs +13 -13
  74. package/dist/src/self-relay/p2p-probe.mjs +168 -168
  75. package/dist/src/self-relay/privacy-page.mjs +26 -26
  76. package/dist/src/self-relay/public-releases.mjs +67 -67
  77. package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
  78. package/dist/src/self-relay/usage-meter.mjs +141 -141
  79. package/dist/src/server.mjs +5 -4
  80. package/dist/src/service-doctor.mjs +148 -148
  81. package/dist/src/service-install.mjs +97 -97
  82. package/dist/src/service-lifecycle.mjs +139 -139
  83. package/dist/src/shared-app-server.mjs +666 -666
  84. package/dist/src/shared-catalog.mjs +132 -132
  85. package/dist/src/shared-thread-stream.mjs +165 -165
  86. package/dist/src/usage-reset.mjs +17 -17
  87. package/dist/src/workstation-downloads.mjs +120 -120
  88. package/package.json +4 -4
  89. package/tools/postinstall.mjs +186 -186
@@ -1,120 +1,120 @@
1
- import fs from 'node:fs/promises';
2
- import { constants } from 'node:fs';
3
- import path from 'node:path';
4
- import crypto from 'node:crypto';
5
- import { fileRevision, FILE_PART_BYTES, FILE_READ_LIMIT } from './file-transfer-range.mjs';
6
- import { readRecord, writeRecord } from './service-lifecycle.mjs';
7
-
8
- const fail = (message, statusCode = 400) => { throw Object.assign(new Error(message), { statusCode }); };
9
- const pathKey = value => process.platform === 'win32' ? value.toLowerCase() : value;
10
- const validName = value => typeof value === 'string' && value.length > 0
11
- && !/[<>:"/\\|?*\x00-\x1f]/.test(value) && !/[ .]$/.test(value) && !['.', '..'].includes(value)
12
- && !/^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(value);
13
-
14
- // A saved copy belongs to the authorized external asset, rather than its current chat page.
15
- export class WorkstationDownloads {
16
- constructor(stateRoot) {
17
- this.recordPath = path.join(stateRoot, 'external-file-copies.json');
18
- this.copies = readRecord(this.recordPath) || {};
19
- }
20
-
21
- async copy(sourceId) {
22
- const record = this.copies[sourceId];
23
- if (!record) return null;
24
- try {
25
- const stat = await fs.stat(record.path);
26
- return stat.isFile() && fileRevision(stat) === record.revision && stat.size === record.size ? record : null;
27
- } catch (error) { if (['ENOENT', 'ENOTDIR'].includes(error.code)) return null; throw error; }
28
- }
29
-
30
- async folder(rpc, locations, selection) {
31
- let root;
32
- if (selection.target === 'downloads') root = locations.downloads;
33
- else if (selection.target === 'project' && typeof selection.projectId === 'string' && selection.projectId) {
34
- const { project } = await rpc('project/read', { projectId: selection.projectId });
35
- const selected = selection.root || project.roots[0]?.path;
36
- root = project.roots.find(item => pathKey(item.path) === pathKey(selected))?.path;
37
- }
38
- if (!root || !path.isAbsolute(root)) fail('请选择电脑项目或下载目录。');
39
- const relative = selection.path || '';
40
- if (typeof relative !== 'string' || relative.split('/').some(part => part && !validName(part))) fail('子文件夹路径无效。');
41
- const realRoot = await fs.realpath(root), target = path.join(realRoot, ...relative.split('/'));
42
- const realTarget = await fs.realpath(target), difference = path.relative(realRoot, realTarget);
43
- if (difference === '..' || difference.startsWith('..' + path.sep) || path.isAbsolute(difference)) fail('只能选择当前目录内的子文件夹。');
44
- if (!(await fs.stat(realTarget)).isDirectory()) fail('所选路径不是文件夹。');
45
- return { root, path: relative, absolutePath: target, realPath: realTarget,
46
- parent: relative ? relative.split('/').slice(0, -1).join('/') : null };
47
- }
48
-
49
- async browse(rpc, locations, selection) {
50
- const folder = await this.folder(rpc, locations, selection);
51
- const entries = (await fs.readdir(folder.realPath, { withFileTypes: true })).filter(entry => entry.isDirectory())
52
- .map(entry => ({ name: entry.name, path: [folder.path, entry.name].filter(Boolean).join('/') }))
53
- .sort((a, b) => a.name.localeCompare(b.name, 'zh-CN', { numeric: true }));
54
- const { realPath, ...visible } = folder;
55
- return { ...visible, entries };
56
- }
57
-
58
- async save(rpc, locations, selection, asset, readPart, signal) {
59
- const folder = await this.folder(rpc, locations, selection);
60
- const partial = path.join(folder.realPath, `.code-relax-${crypto.randomUUID()}.download.part`);
61
- let handle, result, created = false;
62
- try {
63
- signal?.throwIfAborted();
64
- handle = await fs.open(partial, 'wx', 0o600);
65
- created = true;
66
- let offset = 0, size, revision, name, mimeType;
67
- do {
68
- signal?.throwIfAborted();
69
- const query = new URLSearchParams({ offset: String(offset), length: String(FILE_PART_BYTES) });
70
- if (revision) query.set('revision', revision);
71
- const part = await readPart(asset, query);
72
- signal?.throwIfAborted();
73
- if (offset === 0) {
74
- size = part.size; revision = part.revision; name = part.name || asset.name; mimeType = part.mimeType || asset.mimeType;
75
- if (!validName(name)) fail('文件名无效,请修正来源文件的名称。');
76
- if (!Number.isSafeInteger(size) || size < 1 || size > FILE_READ_LIMIT || !revision) fail('文件大小无效或超过 20 MiB 上限。');
77
- }
78
- if (part.size !== size || part.revision !== revision || part.offset !== offset
79
- || !part.bytes?.length || part.bytes.length > Math.min(FILE_PART_BYTES, size - offset)) fail('文件内容发生变化或分片不完整。', 409);
80
- let written = 0;
81
- while (written < part.bytes.length) {
82
- const { bytesWritten } = await handle.write(part.bytes, written, part.bytes.length - written, offset + written);
83
- if (!bytesWritten) fail('电脑文件写入不完整。', 502);
84
- written += bytesWritten;
85
- }
86
- offset += part.bytes.length;
87
- } while (offset < size);
88
- await handle.close(); handle = null;
89
- signal?.throwIfAborted();
90
- // Recheck the destination after the remote read, before creating the final copy.
91
- const current = await this.folder(rpc, locations, selection);
92
- if (pathKey(current.realPath) !== pathKey(folder.realPath)) fail('目标目录发生变化,请重新选择。', 409);
93
- const extension = path.extname(name), body = name.slice(0, name.length - extension.length);
94
- for (let number = 0; ; number++) {
95
- const filename = number ? `${body} (${number})${extension}` : name;
96
- const destination = path.join(folder.realPath, filename);
97
- try {
98
- await fs.copyFile(partial, destination, constants.COPYFILE_EXCL);
99
- const stat = await fs.stat(destination);
100
- if (stat.size !== size) { await fs.unlink(destination); fail('电脑副本大小核对失败。', 502); }
101
- result = { path: destination, name: filename, size, revision: fileRevision(stat), mimeType };
102
- break;
103
- } catch (error) { if (error.code !== 'EEXIST') throw error; }
104
- }
105
- this.copies[asset.id] = result;
106
- try { writeRecord(this.recordPath, this.copies); }
107
- catch (error) { result.warning = '文件已保存,副本记录未能持久保存;重启后请从原文件重新选择。'; console.error('电脑副本记录保存失败', error); }
108
- return result;
109
- } finally {
110
- await handle?.close();
111
- if (created) {
112
- try { await fs.rm(partial, { force: true }); }
113
- catch (error) {
114
- if (!result) throw error;
115
- result.warning = '文件已保存,下载临时文件未能清理。'; console.error('电脑下载临时文件清理失败', error);
116
- }
117
- }
118
- }
119
- }
120
- }
1
+ import fs from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { fileRevision, FILE_PART_BYTES, FILE_READ_LIMIT } from './file-transfer-range.mjs';
6
+ import { readRecord, writeRecord } from './service-lifecycle.mjs';
7
+
8
+ const fail = (message, statusCode = 400) => { throw Object.assign(new Error(message), { statusCode }); };
9
+ const pathKey = value => process.platform === 'win32' ? value.toLowerCase() : value;
10
+ const validName = value => typeof value === 'string' && value.length > 0
11
+ && !/[<>:"/\\|?*\x00-\x1f]/.test(value) && !/[ .]$/.test(value) && !['.', '..'].includes(value)
12
+ && !/^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(value);
13
+
14
+ // A saved copy belongs to the authorized external asset, rather than its current chat page.
15
+ export class WorkstationDownloads {
16
+ constructor(stateRoot) {
17
+ this.recordPath = path.join(stateRoot, 'external-file-copies.json');
18
+ this.copies = readRecord(this.recordPath) || {};
19
+ }
20
+
21
+ async copy(sourceId) {
22
+ const record = this.copies[sourceId];
23
+ if (!record) return null;
24
+ try {
25
+ const stat = await fs.stat(record.path);
26
+ return stat.isFile() && fileRevision(stat) === record.revision && stat.size === record.size ? record : null;
27
+ } catch (error) { if (['ENOENT', 'ENOTDIR'].includes(error.code)) return null; throw error; }
28
+ }
29
+
30
+ async folder(rpc, locations, selection) {
31
+ let root;
32
+ if (selection.target === 'downloads') root = locations.downloads;
33
+ else if (selection.target === 'project' && typeof selection.projectId === 'string' && selection.projectId) {
34
+ const { project } = await rpc('project/read', { projectId: selection.projectId });
35
+ const selected = selection.root || project.roots[0]?.path;
36
+ root = project.roots.find(item => pathKey(item.path) === pathKey(selected))?.path;
37
+ }
38
+ if (!root || !path.isAbsolute(root)) fail('请选择电脑项目或下载目录。');
39
+ const relative = selection.path || '';
40
+ if (typeof relative !== 'string' || relative.split('/').some(part => part && !validName(part))) fail('子文件夹路径无效。');
41
+ const realRoot = await fs.realpath(root), target = path.join(realRoot, ...relative.split('/'));
42
+ const realTarget = await fs.realpath(target), difference = path.relative(realRoot, realTarget);
43
+ if (difference === '..' || difference.startsWith('..' + path.sep) || path.isAbsolute(difference)) fail('只能选择当前目录内的子文件夹。');
44
+ if (!(await fs.stat(realTarget)).isDirectory()) fail('所选路径不是文件夹。');
45
+ return { root, path: relative, absolutePath: target, realPath: realTarget,
46
+ parent: relative ? relative.split('/').slice(0, -1).join('/') : null };
47
+ }
48
+
49
+ async browse(rpc, locations, selection) {
50
+ const folder = await this.folder(rpc, locations, selection);
51
+ const entries = (await fs.readdir(folder.realPath, { withFileTypes: true })).filter(entry => entry.isDirectory())
52
+ .map(entry => ({ name: entry.name, path: [folder.path, entry.name].filter(Boolean).join('/') }))
53
+ .sort((a, b) => a.name.localeCompare(b.name, 'zh-CN', { numeric: true }));
54
+ const { realPath, ...visible } = folder;
55
+ return { ...visible, entries };
56
+ }
57
+
58
+ async save(rpc, locations, selection, asset, readPart, signal) {
59
+ const folder = await this.folder(rpc, locations, selection);
60
+ const partial = path.join(folder.realPath, `.code-relax-${crypto.randomUUID()}.download.part`);
61
+ let handle, result, created = false;
62
+ try {
63
+ signal?.throwIfAborted();
64
+ handle = await fs.open(partial, 'wx', 0o600);
65
+ created = true;
66
+ let offset = 0, size, revision, name, mimeType;
67
+ do {
68
+ signal?.throwIfAborted();
69
+ const query = new URLSearchParams({ offset: String(offset), length: String(FILE_PART_BYTES) });
70
+ if (revision) query.set('revision', revision);
71
+ const part = await readPart(asset, query);
72
+ signal?.throwIfAborted();
73
+ if (offset === 0) {
74
+ size = part.size; revision = part.revision; name = part.name || asset.name; mimeType = part.mimeType || asset.mimeType;
75
+ if (!validName(name)) fail('文件名无效,请修正来源文件的名称。');
76
+ if (!Number.isSafeInteger(size) || size < 1 || size > FILE_READ_LIMIT || !revision) fail('文件大小无效或超过 20 MiB 上限。');
77
+ }
78
+ if (part.size !== size || part.revision !== revision || part.offset !== offset
79
+ || !part.bytes?.length || part.bytes.length > Math.min(FILE_PART_BYTES, size - offset)) fail('文件内容发生变化或分片不完整。', 409);
80
+ let written = 0;
81
+ while (written < part.bytes.length) {
82
+ const { bytesWritten } = await handle.write(part.bytes, written, part.bytes.length - written, offset + written);
83
+ if (!bytesWritten) fail('电脑文件写入不完整。', 502);
84
+ written += bytesWritten;
85
+ }
86
+ offset += part.bytes.length;
87
+ } while (offset < size);
88
+ await handle.close(); handle = null;
89
+ signal?.throwIfAborted();
90
+ // Recheck the destination after the remote read, before creating the final copy.
91
+ const current = await this.folder(rpc, locations, selection);
92
+ if (pathKey(current.realPath) !== pathKey(folder.realPath)) fail('目标目录发生变化,请重新选择。', 409);
93
+ const extension = path.extname(name), body = name.slice(0, name.length - extension.length);
94
+ for (let number = 0; ; number++) {
95
+ const filename = number ? `${body} (${number})${extension}` : name;
96
+ const destination = path.join(folder.realPath, filename);
97
+ try {
98
+ await fs.copyFile(partial, destination, constants.COPYFILE_EXCL);
99
+ const stat = await fs.stat(destination);
100
+ if (stat.size !== size) { await fs.unlink(destination); fail('电脑副本大小核对失败。', 502); }
101
+ result = { path: destination, name: filename, size, revision: fileRevision(stat), mimeType };
102
+ break;
103
+ } catch (error) { if (error.code !== 'EEXIST') throw error; }
104
+ }
105
+ this.copies[asset.id] = result;
106
+ try { writeRecord(this.recordPath, this.copies); }
107
+ catch (error) { result.warning = '文件已保存,副本记录未能持久保存;重启后请从原文件重新选择。'; console.error('电脑副本记录保存失败', error); }
108
+ return result;
109
+ } finally {
110
+ await handle?.close();
111
+ if (created) {
112
+ try { await fs.rm(partial, { force: true }); }
113
+ catch (error) {
114
+ if (!result) throw error;
115
+ result.warning = '文件已保存,下载临时文件未能清理。'; console.error('电脑下载临时文件清理失败', error);
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walkhi/code-relax",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.51",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -41,8 +41,8 @@
41
41
  "tag": "beta"
42
42
  },
43
43
  "optionalDependencies": {
44
- "@walkhi/code-relax-native-win32-x64": "0.1.0-beta.50",
45
- "@walkhi/code-relax-native-darwin-arm64": "0.1.0-beta.50",
46
- "@walkhi/code-relax-native-darwin-x64": "0.1.0-beta.50"
44
+ "@walkhi/code-relax-native-win32-x64": "0.1.0-beta.51",
45
+ "@walkhi/code-relax-native-darwin-arm64": "0.1.0-beta.51",
46
+ "@walkhi/code-relax-native-darwin-x64": "0.1.0-beta.51"
47
47
  }
48
48
  }