@walkhi/code-relax 0.1.0-beta.30 → 0.1.0-beta.32
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 +95 -89
- package/dist/bin/cleanup-accounts.mjs +63 -63
- package/dist/bin/codex-remote.mjs +374 -367
- package/dist/bin/manage-relay-admin.mjs +20 -20
- package/dist/bin/migrate-relay-db.mjs +109 -109
- package/dist/bin/publish-android-release.mjs +63 -58
- package/dist/bin/publish-harmony-release.mjs +46 -0
- package/dist/bin/release-upload-server.mjs +174 -0
- package/dist/bin/self-relay-admin.mjs +8 -8
- package/dist/bin/self-relay-demo.mjs +33 -33
- package/dist/bin/self-relay-server.mjs +66 -66
- package/dist/build-platform.json +1 -1
- package/dist/get-lan-networks.ps1 +40 -40
- package/dist/get-shared-service-url.ps1 +6 -6
- package/dist/shared/app-server-events.cjs +422 -412
- package/dist/shared/code-browser.cjs +159 -159
- package/dist/shared/file-changes.cjs +31 -31
- package/dist/shared/file-citations.cjs +117 -117
- package/dist/shared/file-mentions.cjs +92 -92
- package/dist/shared/message-delivery.cjs +54 -54
- package/dist/shared/p2p-data-channel.cjs +72 -72
- package/dist/shared/p2p-diagnostics.cjs +28 -28
- package/dist/shared/p2p-file-channel.cjs +78 -74
- package/dist/shared/plan-pricing.cjs +32 -32
- package/dist/shared/question-attention.cjs +69 -0
- package/dist/shared/server-requests.cjs +219 -219
- package/dist/shared/timeline-sync.cjs +173 -173
- package/dist/skills/code-relax/SKILL.md +22 -22
- package/dist/src/archived-threads.mjs +22 -0
- package/dist/src/background-process.mjs +18 -18
- package/dist/src/bounded-process-log.mjs +63 -63
- package/dist/src/celsius-client.mjs +107 -107
- package/dist/src/code-browser.mjs +100 -100
- package/dist/src/desktop-launcher.mjs +46 -46
- package/dist/src/dots-approval-timeline.mjs +99 -99
- package/dist/src/dots-avatar.mjs +42 -42
- package/dist/src/dots-chat-projection.mjs +50 -50
- package/dist/src/dots-chat-server.mjs +461 -461
- package/dist/src/dots-message-images.mjs +319 -319
- package/dist/src/dots-profile.mjs +77 -77
- package/dist/src/dots-room-activity.mjs +94 -94
- package/dist/src/dots-room-client.mjs +135 -135
- package/dist/src/dots-room-projection.mjs +94 -94
- package/dist/src/dots-schedules.mjs +66 -66
- package/dist/src/dots-summary-tasks.mjs +32 -32
- package/dist/src/dots-task-catalog.mjs +430 -430
- package/dist/src/dots-task-files.mjs +52 -52
- package/dist/src/execution-health.mjs +26 -26
- package/dist/src/file-transfer-range.mjs +22 -22
- package/dist/src/file-upload-session.mjs +130 -130
- package/dist/src/hosted-history-app-server.mjs +103 -103
- package/dist/src/hosted-history-cache.mjs +113 -113
- package/dist/src/lan-socket-server.mjs +265 -263
- package/dist/src/managed-app-server.mjs +244 -244
- package/dist/src/message-images.mjs +9 -9
- package/dist/src/message-upload-files.mjs +74 -74
- package/dist/src/onboarding.mjs +12 -12
- package/dist/src/plan-pricing.mjs +47 -47
- package/dist/src/platform/README.md +12 -12
- package/dist/src/platform/codex-protocol.mjs +46 -46
- package/dist/src/platform/codex-runtime.mjs +3 -3
- package/dist/src/platform/credential-store.mjs +3 -3
- package/dist/src/platform/desktop-host.mjs +3 -3
- package/dist/src/platform/desktop-project-registration.mjs +3 -3
- package/dist/src/platform/desktop-project-store.mjs +208 -149
- package/dist/src/platform/file-opener.mjs +9 -0
- package/dist/src/platform/linux/README.md +3 -3
- package/dist/src/platform/linux/codex-runtime.mjs +56 -0
- package/dist/src/platform/linux/credential-store.mjs +28 -0
- package/dist/src/platform/linux/desktop-host.mjs +83 -0
- package/dist/src/platform/linux/desktop-installation.mjs +36 -0
- package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
- package/dist/src/platform/linux/network-info.mjs +11 -0
- package/dist/src/platform/linux/process.mjs +42 -0
- package/dist/src/platform/linux/project-locations.mjs +17 -0
- package/dist/src/platform/linux/resident-startup.mjs +52 -0
- package/dist/src/platform/linux/retained-runtime.mjs +76 -0
- package/dist/src/platform/linux/service-host.mjs +18 -0
- package/dist/src/platform/linux/system.mjs +49 -0
- package/dist/src/platform/linux/user-environment.mjs +7 -0
- package/dist/src/platform/macos/README.md +3 -3
- package/dist/src/platform/macos/codex-runtime.mjs +125 -125
- package/dist/src/platform/macos/credential-store.mjs +10 -10
- package/dist/src/platform/macos/desktop-host.mjs +82 -82
- package/dist/src/platform/macos/desktop-project-registration.mjs +11 -10
- package/dist/src/platform/macos/network-info.mjs +8 -28
- package/dist/src/platform/macos/project-locations.mjs +8 -8
- package/dist/src/platform/macos/resident-startup.mjs +84 -84
- package/dist/src/platform/macos/retained-runtime.mjs +127 -127
- package/dist/src/platform/macos/service-host.mjs +18 -18
- package/dist/src/platform/macos/system.mjs +29 -29
- package/dist/src/platform/macos/user-environment.mjs +7 -7
- package/dist/src/platform/native-package.mjs +58 -46
- package/dist/src/platform/network-info.mjs +3 -3
- package/dist/src/platform/path-identity.mjs +5 -5
- package/dist/src/platform/posix/network-candidates.mjs +23 -0
- package/dist/src/platform/posix/runtime-paths.mjs +25 -25
- package/dist/src/platform/project-locations.mjs +3 -3
- package/dist/src/platform/runtime-paths.mjs +8 -8
- package/dist/src/platform/select.mjs +5 -5
- package/dist/src/platform/service-host.mjs +3 -3
- package/dist/src/platform/user-environment.mjs +3 -3
- package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
- package/dist/src/platform/windows/activate-desktop.ps1 +48 -48
- package/dist/src/platform/windows/background-process.mjs +30 -30
- package/dist/src/platform/windows/codex-runtime.mjs +215 -215
- package/dist/src/platform/windows/credential-store.mjs +27 -27
- package/dist/src/platform/windows/credential-store.ps1 +49 -49
- package/dist/src/platform/windows/desktop-helper.mjs +179 -179
- package/dist/src/platform/windows/desktop-host.mjs +58 -58
- package/dist/src/platform/windows/desktop-info.ps1 +14 -14
- package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
- package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -21
- package/dist/src/platform/windows/dots-profile.mjs +49 -49
- package/dist/src/platform/windows/launch-worker.mjs +41 -41
- package/dist/src/platform/windows/network-info.mjs +34 -34
- package/dist/src/platform/windows/project-locations.mjs +16 -16
- package/dist/src/platform/windows/resident-start.js +20 -20
- package/dist/src/platform/windows/resident-start.ps1 +7 -7
- package/dist/src/platform/windows/resident-startup.ps1 +83 -83
- package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
- package/dist/src/platform/windows/runtime-paths.mjs +20 -20
- package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
- package/dist/src/platform/windows/service-host.mjs +31 -31
- package/dist/src/platform/windows/start-hidden-console.ps1 +49 -49
- package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
- package/dist/src/platform/windows/user-environment.mjs +50 -50
- package/dist/src/platform/windows/user-environment.ps1 +41 -41
- package/dist/src/project-archive.mjs +30 -0
- package/dist/src/project-create.mjs +121 -119
- package/dist/src/project-management.mjs +59 -69
- package/dist/src/project-remove.mjs +55 -0
- package/dist/src/project-update.mjs +56 -0
- package/dist/src/runtime-discovery.mjs +47 -47
- package/dist/src/self-relay/account-cleanup.mjs +30 -30
- package/dist/src/self-relay/account-connections.mjs +52 -52
- package/dist/src/self-relay/admin-cloud.mjs +101 -101
- package/dist/src/self-relay/admin-page.mjs +80 -80
- package/dist/src/self-relay/admin-server.mjs +74 -74
- package/dist/src/self-relay/admin-state.mjs +127 -127
- package/dist/src/self-relay/admin-style.mjs +14 -14
- package/dist/src/self-relay/android-releases.mjs +61 -61
- package/dist/src/self-relay/client.mjs +61 -61
- package/dist/src/self-relay/connector.mjs +439 -438
- package/dist/src/self-relay/debug-allowlist.mjs +33 -33
- package/dist/src/self-relay/demo.mjs +37 -37
- package/dist/src/self-relay/device-info.mjs +27 -0
- package/dist/src/self-relay/device-store.mjs +92 -84
- package/dist/src/self-relay/diagnostics.mjs +54 -54
- package/dist/src/self-relay/email-accounts.mjs +183 -183
- package/dist/src/self-relay/file-wire.mjs +13 -13
- package/dist/src/self-relay/lan-authorizations.mjs +71 -42
- package/dist/src/self-relay/lifecycle.mjs +264 -250
- package/dist/src/self-relay/long-pairing.mjs +40 -40
- package/dist/src/self-relay/mysql-state.mjs +118 -105
- package/dist/src/self-relay/p2p-probe.mjs +159 -159
- package/dist/src/self-relay/privacy-page.mjs +26 -26
- package/dist/src/self-relay/public-releases.mjs +52 -0
- package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
- package/dist/src/self-relay/server.mjs +1006 -932
- package/dist/src/self-relay/usage-daily.mjs +55 -55
- package/dist/src/self-relay/usage-meter.mjs +141 -141
- package/dist/src/self-relay/windows-route-helper.mjs +75 -75
- package/dist/src/self-relay/wire.mjs +36 -36
- package/dist/src/server.mjs +1861 -1778
- package/dist/src/service-control.mjs +82 -82
- package/dist/src/service-doctor.mjs +110 -110
- package/dist/src/service-health.mjs +33 -33
- package/dist/src/service-install.mjs +91 -91
- package/dist/src/service-lifecycle.mjs +138 -138
- package/dist/src/service-restart.mjs +81 -81
- package/dist/src/shared-app-server.mjs +615 -611
- package/dist/src/shared-catalog.mjs +132 -132
- package/dist/src/shared-recovery.mjs +146 -146
- package/dist/src/shared-thread-stream.mjs +164 -164
- package/dist/src/skill-install.mjs +51 -51
- package/dist/src/thread-title-generation.mjs +150 -150
- package/dist/src/turn-model-history.mjs +47 -47
- package/dist/src/usage-reset.mjs +17 -17
- package/dist/src/workstation-downloads.mjs +120 -120
- package/dist/tools/rtc-route-helper.ps1 +99 -99
- package/dist/tools/start-rtc-route-helper.ps1 +15 -15
- package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
- package/dist/web/vendor/markdown-it.umd.min.js +13 -13
- package/package.json +7 -6
- package/tools/postinstall.mjs +127 -127
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
import { dotsProfile } from '../../dots-profile.mjs';
|
|
5
|
-
|
|
6
|
-
// Desktop 26.928.2636.0 persists its current TBO sidebar profiles here.
|
|
7
|
-
// Read display names, appearance colors and snapshot references; identity comes from the authenticated kernel.
|
|
8
|
-
export function desktopDotsProfiles(codexDataRoot, stateRoot) {
|
|
9
|
-
const snapshotPath = ({ accountId, userId }) => stateRoot && path.join(stateRoot, 'dots-profiles',
|
|
10
|
-
createHash('sha256').update(JSON.stringify([accountId, userId])).digest('hex') + '.json');
|
|
11
|
-
const read = async ({ accountId, userId }) => {
|
|
12
|
-
let state;
|
|
13
|
-
try {
|
|
14
|
-
state = JSON.parse(await fs.readFile(path.join(codexDataRoot, '.codex-global-state.json'), 'utf8'));
|
|
15
|
-
} catch (error) {
|
|
16
|
-
if (error.code !== 'ENOENT') throw error;
|
|
17
|
-
}
|
|
18
|
-
const cache = state?.['electron-persisted-atom-state']?.['cloud-aeon-sidebar-cache-v1'];
|
|
19
|
-
const names = new Map();
|
|
20
|
-
if (!accountId || !userId) return names;
|
|
21
|
-
const matching = cache?.hostId === 'durable' && cache.accountId === accountId && cache.userId === userId;
|
|
22
|
-
for (const [threadId, profile] of Object.entries(matching ? cache.profilesByThreadId || {} : {})) {
|
|
23
|
-
const value = dotsProfile(profile);
|
|
24
|
-
for (const id of [threadId, profile.active_root_thread_id, profile.root_thread_id]) {
|
|
25
|
-
if (typeof id === 'string' && id) names.set(id, value);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const file = snapshotPath({ accountId, userId });
|
|
29
|
-
if (file) {
|
|
30
|
-
try {
|
|
31
|
-
const saved = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
32
|
-
if (saved.accountId !== accountId || saved.userId !== userId || !Array.isArray(saved.profiles)) throw new Error('点点资料缓存身份无效。');
|
|
33
|
-
const profiles = new Map(saved.profiles);
|
|
34
|
-
const byId = new Map([...profiles.values()].map(profile => [profile.profileId, profile]));
|
|
35
|
-
for (const [id, profile] of names) if (byId.has(profile.profileId)) names.set(id, byId.get(profile.profileId));
|
|
36
|
-
for (const [id, profile] of profiles) names.set(id, profile);
|
|
37
|
-
} catch (error) { if (error.code !== 'ENOENT') console.error('读取点点资料缓存失败:', error.message); }
|
|
38
|
-
}
|
|
39
|
-
return names;
|
|
40
|
-
};
|
|
41
|
-
read.save = async (identity, profiles) => {
|
|
42
|
-
const file = snapshotPath(identity);
|
|
43
|
-
if (!file) return;
|
|
44
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
45
|
-
await fs.writeFile(file + '.tmp', JSON.stringify({ ...identity, profiles: [...profiles] }), 'utf8');
|
|
46
|
-
await fs.rename(file + '.tmp', file);
|
|
47
|
-
};
|
|
48
|
-
return read;
|
|
49
|
-
}
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { dotsProfile } from '../../dots-profile.mjs';
|
|
5
|
+
|
|
6
|
+
// Desktop 26.928.2636.0 persists its current TBO sidebar profiles here.
|
|
7
|
+
// Read display names, appearance colors and snapshot references; identity comes from the authenticated kernel.
|
|
8
|
+
export function desktopDotsProfiles(codexDataRoot, stateRoot) {
|
|
9
|
+
const snapshotPath = ({ accountId, userId }) => stateRoot && path.join(stateRoot, 'dots-profiles',
|
|
10
|
+
createHash('sha256').update(JSON.stringify([accountId, userId])).digest('hex') + '.json');
|
|
11
|
+
const read = async ({ accountId, userId }) => {
|
|
12
|
+
let state;
|
|
13
|
+
try {
|
|
14
|
+
state = JSON.parse(await fs.readFile(path.join(codexDataRoot, '.codex-global-state.json'), 'utf8'));
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error.code !== 'ENOENT') throw error;
|
|
17
|
+
}
|
|
18
|
+
const cache = state?.['electron-persisted-atom-state']?.['cloud-aeon-sidebar-cache-v1'];
|
|
19
|
+
const names = new Map();
|
|
20
|
+
if (!accountId || !userId) return names;
|
|
21
|
+
const matching = cache?.hostId === 'durable' && cache.accountId === accountId && cache.userId === userId;
|
|
22
|
+
for (const [threadId, profile] of Object.entries(matching ? cache.profilesByThreadId || {} : {})) {
|
|
23
|
+
const value = dotsProfile(profile);
|
|
24
|
+
for (const id of [threadId, profile.active_root_thread_id, profile.root_thread_id]) {
|
|
25
|
+
if (typeof id === 'string' && id) names.set(id, value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const file = snapshotPath({ accountId, userId });
|
|
29
|
+
if (file) {
|
|
30
|
+
try {
|
|
31
|
+
const saved = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
32
|
+
if (saved.accountId !== accountId || saved.userId !== userId || !Array.isArray(saved.profiles)) throw new Error('点点资料缓存身份无效。');
|
|
33
|
+
const profiles = new Map(saved.profiles);
|
|
34
|
+
const byId = new Map([...profiles.values()].map(profile => [profile.profileId, profile]));
|
|
35
|
+
for (const [id, profile] of names) if (byId.has(profile.profileId)) names.set(id, byId.get(profile.profileId));
|
|
36
|
+
for (const [id, profile] of profiles) names.set(id, profile);
|
|
37
|
+
} catch (error) { if (error.code !== 'ENOENT') console.error('读取点点资料缓存失败:', error.message); }
|
|
38
|
+
}
|
|
39
|
+
return names;
|
|
40
|
+
};
|
|
41
|
+
read.save = async (identity, profiles) => {
|
|
42
|
+
const file = snapshotPath(identity);
|
|
43
|
+
if (!file) return;
|
|
44
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
45
|
+
await fs.writeFile(file + '.tmp', JSON.stringify({ ...identity, profiles: [...profiles] }), 'utf8');
|
|
46
|
+
await fs.rename(file + '.tmp', file);
|
|
47
|
+
};
|
|
48
|
+
return read;
|
|
49
|
+
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
import { attachProcessLog } from '../../bounded-process-log.mjs';
|
|
4
|
-
|
|
5
|
-
const [requestPath, readyPath, instance, diagnosticPath] = process.argv.slice(2);
|
|
6
|
-
let result = { instance, brokerPid: process.pid };
|
|
7
|
-
|
|
8
|
-
function diagnostic(message) {
|
|
9
|
-
if (!diagnosticPath) return;
|
|
10
|
-
fs.appendFileSync(diagnosticPath, `${new Date().toISOString()} ${message}\n`, 'utf8');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const spec = JSON.parse(fs.readFileSync(requestPath, 'utf8').replace(/^\uFEFF/u, ''));
|
|
15
|
-
diagnostic('request loaded');
|
|
16
|
-
if (spec.instance !== instance) throw new Error('Windows process broker request identity mismatch.');
|
|
17
|
-
fs.rmSync(requestPath, { force: true });
|
|
18
|
-
const logs = spec.logs || [];
|
|
19
|
-
const child = spawn(spec.executable, spec.args || [], {
|
|
20
|
-
windowsHide: true,
|
|
21
|
-
env: spec.environment,
|
|
22
|
-
stdio: ['ignore', logs[0] ? 'pipe' : 'ignore', logs[1] ? 'pipe' : 'ignore'],
|
|
23
|
-
});
|
|
24
|
-
if (child.stdout) attachProcessLog(child.stdout, logs[0]);
|
|
25
|
-
if (child.stderr) attachProcessLog(child.stderr, logs[1]);
|
|
26
|
-
await new Promise((resolve, reject) => {
|
|
27
|
-
child.once('spawn', resolve);
|
|
28
|
-
child.once('error', reject);
|
|
29
|
-
});
|
|
30
|
-
// Keep this WMI-owned broker alive with the target so Codex tools inherit
|
|
31
|
-
// its hidden console instead of creating a visible console per command.
|
|
32
|
-
result.pid = child.pid;
|
|
33
|
-
diagnostic(`target started (PID ${child.pid})`);
|
|
34
|
-
} catch (error) {
|
|
35
|
-
result.error = error?.stack || error?.message || String(error);
|
|
36
|
-
try { diagnostic(`failed: ${result.error}`); } catch {}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const temporary = `${readyPath}.tmp`;
|
|
40
|
-
fs.writeFileSync(temporary, JSON.stringify(result), 'utf8');
|
|
41
|
-
fs.renameSync(temporary, readyPath);
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { attachProcessLog } from '../../bounded-process-log.mjs';
|
|
4
|
+
|
|
5
|
+
const [requestPath, readyPath, instance, diagnosticPath] = process.argv.slice(2);
|
|
6
|
+
let result = { instance, brokerPid: process.pid };
|
|
7
|
+
|
|
8
|
+
function diagnostic(message) {
|
|
9
|
+
if (!diagnosticPath) return;
|
|
10
|
+
fs.appendFileSync(diagnosticPath, `${new Date().toISOString()} ${message}\n`, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const spec = JSON.parse(fs.readFileSync(requestPath, 'utf8').replace(/^\uFEFF/u, ''));
|
|
15
|
+
diagnostic('request loaded');
|
|
16
|
+
if (spec.instance !== instance) throw new Error('Windows process broker request identity mismatch.');
|
|
17
|
+
fs.rmSync(requestPath, { force: true });
|
|
18
|
+
const logs = spec.logs || [];
|
|
19
|
+
const child = spawn(spec.executable, spec.args || [], {
|
|
20
|
+
windowsHide: true,
|
|
21
|
+
env: spec.environment,
|
|
22
|
+
stdio: ['ignore', logs[0] ? 'pipe' : 'ignore', logs[1] ? 'pipe' : 'ignore'],
|
|
23
|
+
});
|
|
24
|
+
if (child.stdout) attachProcessLog(child.stdout, logs[0]);
|
|
25
|
+
if (child.stderr) attachProcessLog(child.stderr, logs[1]);
|
|
26
|
+
await new Promise((resolve, reject) => {
|
|
27
|
+
child.once('spawn', resolve);
|
|
28
|
+
child.once('error', reject);
|
|
29
|
+
});
|
|
30
|
+
// Keep this WMI-owned broker alive with the target so Codex tools inherit
|
|
31
|
+
// its hidden console instead of creating a visible console per command.
|
|
32
|
+
result.pid = child.pid;
|
|
33
|
+
diagnostic(`target started (PID ${child.pid})`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
result.error = error?.stack || error?.message || String(error);
|
|
36
|
+
try { diagnostic(`failed: ${result.error}`); } catch {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const temporary = `${readyPath}.tmp`;
|
|
40
|
+
fs.writeFileSync(temporary, JSON.stringify(result), 'utf8');
|
|
41
|
+
fs.renameSync(temporary, readyPath);
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
|
|
6
|
-
const exec = promisify(execFile);
|
|
7
|
-
const bridgeRoot = fileURLToPath(new URL('../../../', import.meta.url));
|
|
8
|
-
|
|
9
|
-
export async function lanNetworks() {
|
|
10
|
-
const executable = path.join(process.env.SystemRoot, 'System32/WindowsPowerShell/v1.0/powershell.exe');
|
|
11
|
-
const { stdout } = await exec(executable, ['-NoProfile', '-NonInteractive', '-File', path.join(bridgeRoot, 'get-lan-networks.ps1')],
|
|
12
|
-
{ windowsHide: true, encoding: 'utf8' });
|
|
13
|
-
const records = JSON.parse(stdout.trim() || '[]');
|
|
14
|
-
if (!Array.isArray(records) || records.length > 8) throw new Error('Windows 返回了无效的局域网信息');
|
|
15
|
-
return records.map(record => {
|
|
16
|
-
if (!privateIpv4(record.address) || !['wifi', 'ethernet'].includes(record.networkType) ||
|
|
17
|
-
!plainLabel(record.networkName) || !plainLabel(record.interfaceName)) throw new Error('Windows 返回了无效的局域网信息');
|
|
18
|
-
return { address: record.address, networkType: record.networkType,
|
|
19
|
-
networkName: record.networkName.trim(), interfaceName: record.interfaceName.trim() };
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function privateIpv4(value) {
|
|
24
|
-
if (typeof value !== 'string') return false;
|
|
25
|
-
const parts = value.split('.').map(Number);
|
|
26
|
-
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
27
|
-
return parts[0] === 10 || (parts[0] === 192 && parts[1] === 168) ||
|
|
28
|
-
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
|
|
29
|
-
(parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function plainLabel(value) {
|
|
33
|
-
return typeof value === 'string' && value.trim().length > 0 && value.trim().length <= 80 && !/[\u0000-\u001f\u007f]/.test(value);
|
|
34
|
-
}
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const exec = promisify(execFile);
|
|
7
|
+
const bridgeRoot = fileURLToPath(new URL('../../../', import.meta.url));
|
|
8
|
+
|
|
9
|
+
export async function lanNetworks() {
|
|
10
|
+
const executable = path.join(process.env.SystemRoot, 'System32/WindowsPowerShell/v1.0/powershell.exe');
|
|
11
|
+
const { stdout } = await exec(executable, ['-NoProfile', '-NonInteractive', '-File', path.join(bridgeRoot, 'get-lan-networks.ps1')],
|
|
12
|
+
{ windowsHide: true, encoding: 'utf8' });
|
|
13
|
+
const records = JSON.parse(stdout.trim() || '[]');
|
|
14
|
+
if (!Array.isArray(records) || records.length > 8) throw new Error('Windows 返回了无效的局域网信息');
|
|
15
|
+
return records.map(record => {
|
|
16
|
+
if (!privateIpv4(record.address) || !['wifi', 'ethernet'].includes(record.networkType) ||
|
|
17
|
+
!plainLabel(record.networkName) || !plainLabel(record.interfaceName)) throw new Error('Windows 返回了无效的局域网信息');
|
|
18
|
+
return { address: record.address, networkType: record.networkType,
|
|
19
|
+
networkName: record.networkName.trim(), interfaceName: record.interfaceName.trim() };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function privateIpv4(value) {
|
|
24
|
+
if (typeof value !== 'string') return false;
|
|
25
|
+
const parts = value.split('.').map(Number);
|
|
26
|
+
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
27
|
+
return parts[0] === 10 || (parts[0] === 192 && parts[1] === 168) ||
|
|
28
|
+
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
|
|
29
|
+
(parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function plainLabel(value) {
|
|
33
|
+
return typeof value === 'string' && value.trim().length > 0 && value.trim().length <= 80 && !/[\u0000-\u001f\u007f]/.test(value);
|
|
34
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { powershell } from './desktop-host.mjs';
|
|
5
|
-
|
|
6
|
-
const exec = promisify(execFile);
|
|
7
|
-
|
|
8
|
-
export async function projectLocations() {
|
|
9
|
-
const command = "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); $projectDocuments = [Environment]::GetFolderPath('MyDocuments'); $projectUserDirectory = [Environment]::GetFolderPath('UserProfile'); $projectDownloads = [Environment]::ExpandEnvironmentVariables((Get-ItemPropertyValue -LiteralPath 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders' -Name '{374DE290-123F-4565-9164-39C4925E467B}' -ErrorAction Stop)); $projectDriveRoots = @(Get-PSDrive -PSProvider FileSystem | ForEach-Object { $_.Root }); @{ documents = $projectDocuments; home = $projectUserDirectory; downloads = $projectDownloads; roots = $projectDriveRoots } | ConvertTo-Json -Compress";
|
|
10
|
-
const { stdout } = await exec(powershell, ['-NoProfile', '-NonInteractive', '-Command', command],
|
|
11
|
-
{ windowsHide: true, encoding: 'utf8', timeout: 10000 });
|
|
12
|
-
const locations = JSON.parse(stdout);
|
|
13
|
-
if (!path.isAbsolute(locations.documents)) throw new Error('无法取得电脑的文档目录。');
|
|
14
|
-
if (!path.isAbsolute(locations.downloads)) throw new Error('无法取得电脑的下载目录。');
|
|
15
|
-
return { defaultParent: path.join(locations.documents, 'ChatGPT'), home: locations.home, downloads: locations.downloads, roots: locations.roots };
|
|
16
|
-
}
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { powershell } from './desktop-host.mjs';
|
|
5
|
+
|
|
6
|
+
const exec = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
export async function projectLocations() {
|
|
9
|
+
const command = "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); $projectDocuments = [Environment]::GetFolderPath('MyDocuments'); $projectUserDirectory = [Environment]::GetFolderPath('UserProfile'); $projectDownloads = [Environment]::ExpandEnvironmentVariables((Get-ItemPropertyValue -LiteralPath 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders' -Name '{374DE290-123F-4565-9164-39C4925E467B}' -ErrorAction Stop)); $projectDriveRoots = @(Get-PSDrive -PSProvider FileSystem | ForEach-Object { $_.Root }); @{ documents = $projectDocuments; home = $projectUserDirectory; downloads = $projectDownloads; roots = $projectDriveRoots } | ConvertTo-Json -Compress";
|
|
10
|
+
const { stdout } = await exec(powershell, ['-NoProfile', '-NonInteractive', '-Command', command],
|
|
11
|
+
{ windowsHide: true, encoding: 'utf8', timeout: 10000 });
|
|
12
|
+
const locations = JSON.parse(stdout);
|
|
13
|
+
if (!path.isAbsolute(locations.documents)) throw new Error('无法取得电脑的文档目录。');
|
|
14
|
+
if (!path.isAbsolute(locations.downloads)) throw new Error('无法取得电脑的下载目录。');
|
|
15
|
+
return { defaultParent: path.join(locations.documents, 'ChatGPT'), home: locations.home, downloads: locations.downloads, roots: locations.roots };
|
|
16
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
// Windows Script Host has no console. Keep the login task alive until the
|
|
2
|
-
// short-lived CLI has handed the resident service to the background broker.
|
|
3
|
-
if (WScript.Arguments.length !== 2) WScript.Quit(2);
|
|
4
|
-
var shell = new ActiveXObject('WScript.Shell');
|
|
5
|
-
var files = new ActiveXObject('Scripting.FileSystemObject');
|
|
6
|
-
var node = WScript.Arguments.Item(0);
|
|
7
|
-
var entry = WScript.Arguments.Item(1);
|
|
8
|
-
if (!files.FileExists(node)) {
|
|
9
|
-
var directories = shell.Environment('PROCESS').Item('PATH').split(';');
|
|
10
|
-
node = '';
|
|
11
|
-
for (var i = 0; i < directories.length; i++) {
|
|
12
|
-
var directory = directories[i].replace(/^"|"$/g, '');
|
|
13
|
-
if (!directory) continue;
|
|
14
|
-
var candidate = files.BuildPath(directory, 'node.exe');
|
|
15
|
-
if (files.FileExists(candidate)) { node = candidate; break; }
|
|
16
|
-
}
|
|
17
|
-
if (!node) WScript.Quit(2);
|
|
18
|
-
}
|
|
19
|
-
var command = '"' + node + '" "' + entry + '" start --json';
|
|
20
|
-
WScript.Quit(shell.Run(command, 0, true));
|
|
1
|
+
// Windows Script Host has no console. Keep the login task alive until the
|
|
2
|
+
// short-lived CLI has handed the resident service to the background broker.
|
|
3
|
+
if (WScript.Arguments.length !== 2) WScript.Quit(2);
|
|
4
|
+
var shell = new ActiveXObject('WScript.Shell');
|
|
5
|
+
var files = new ActiveXObject('Scripting.FileSystemObject');
|
|
6
|
+
var node = WScript.Arguments.Item(0);
|
|
7
|
+
var entry = WScript.Arguments.Item(1);
|
|
8
|
+
if (!files.FileExists(node)) {
|
|
9
|
+
var directories = shell.Environment('PROCESS').Item('PATH').split(';');
|
|
10
|
+
node = '';
|
|
11
|
+
for (var i = 0; i < directories.length; i++) {
|
|
12
|
+
var directory = directories[i].replace(/^"|"$/g, '');
|
|
13
|
+
if (!directory) continue;
|
|
14
|
+
var candidate = files.BuildPath(directory, 'node.exe');
|
|
15
|
+
if (files.FileExists(candidate)) { node = candidate; break; }
|
|
16
|
+
}
|
|
17
|
+
if (!node) WScript.Quit(2);
|
|
18
|
+
}
|
|
19
|
+
var command = '"' + node + '" "' + entry + '" start --json';
|
|
20
|
+
WScript.Quit(shell.Run(command, 0, true));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
param([Parameter(Mandatory=$true)][string]$NodePath, [Parameter(Mandatory=$true)][string]$Entry)
|
|
2
|
-
$ErrorActionPreference = 'Stop'
|
|
3
|
-
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
4
|
-
$state = Join-Path $env:LOCALAPPDATA 'CodexRemote'
|
|
5
|
-
New-Item -ItemType Directory -Path $state -Force | Out-Null
|
|
6
|
-
& $NodePath $Entry start --json >> (Join-Path $state 'resident-start.log') 2>&1
|
|
7
|
-
exit $LASTEXITCODE
|
|
1
|
+
param([Parameter(Mandatory=$true)][string]$NodePath, [Parameter(Mandatory=$true)][string]$Entry)
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
4
|
+
$state = Join-Path $env:LOCALAPPDATA 'CodexRemote'
|
|
5
|
+
New-Item -ItemType Directory -Path $state -Force | Out-Null
|
|
6
|
+
& $NodePath $Entry start --json >> (Join-Path $state 'resident-start.log') 2>&1
|
|
7
|
+
exit $LASTEXITCODE
|
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
param([string]$NodePath, [string]$Entry, [ValidateSet('enable','disable','status')][string]$Action='status')
|
|
2
|
-
$ErrorActionPreference = 'Stop'
|
|
3
|
-
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
4
|
-
|
|
5
|
-
$taskName = 'Code Relax 桌面服务'
|
|
6
|
-
$legacyTaskName = 'Code Relax Bridge'
|
|
7
|
-
$taskSource = '@walkhi/code-relax'
|
|
8
|
-
$taskPath = '\' + $taskName
|
|
9
|
-
$launcher = Join-Path $PSScriptRoot 'resident-start.js'
|
|
10
|
-
$wscript = Join-Path $env:WINDIR 'System32\wscript.exe'
|
|
11
|
-
$arguments = '//B //Nologo "' + $launcher + '" "' + $NodePath + '" "' + $Entry + '"'
|
|
12
|
-
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
13
|
-
|
|
14
|
-
$scheduler = New-Object -ComObject 'Schedule.Service'
|
|
15
|
-
$scheduler.Connect()
|
|
16
|
-
$root = $scheduler.GetFolder('\')
|
|
17
|
-
$registered = @($root.GetTasks(1) | Where-Object { $_.Name -eq $taskName } | Select-Object -First 1)
|
|
18
|
-
$legacyRegistered = @($root.GetTasks(1) | Where-Object { $_.Name -eq $legacyTaskName } | Select-Object -First 1)
|
|
19
|
-
if ($registered.Count -gt 0 -and $registered[0].Definition.RegistrationInfo.Source -ne $taskSource) {
|
|
20
|
-
throw 'Scheduled task is not owned by this installation.'
|
|
21
|
-
}
|
|
22
|
-
if ($legacyRegistered.Count -gt 0 -and $legacyRegistered[0].Definition.RegistrationInfo.Source -ne $taskSource) {
|
|
23
|
-
throw 'Legacy scheduled task is not owned by this installation.'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
# Remove only the legacy shortcut created by this package. A Startup LNK that
|
|
27
|
-
# launches hidden PowerShell with ExecutionPolicy Bypass triggers AV heuristics.
|
|
28
|
-
$shell = New-Object -ComObject WScript.Shell
|
|
29
|
-
$legacyPath = Join-Path $shell.SpecialFolders.Item('Startup') 'Code Relax Bridge.lnk'
|
|
30
|
-
$legacyLauncher = Join-Path $PSScriptRoot 'resident-start.ps1'
|
|
31
|
-
$legacyExists = Test-Path -LiteralPath $legacyPath
|
|
32
|
-
if ($Action -ne 'status' -and $legacyExists) {
|
|
33
|
-
$legacy = $shell.CreateShortcut($legacyPath)
|
|
34
|
-
if (-not $legacy.Arguments.Contains('"' + $legacyLauncher + '"')) {
|
|
35
|
-
throw 'Legacy startup shortcut is not owned by this installation.'
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if ($Action -eq 'enable') {
|
|
40
|
-
if (-not (Test-Path -LiteralPath $NodePath) -or -not (Test-Path -LiteralPath $Entry) -or
|
|
41
|
-
-not (Test-Path -LiteralPath $launcher) -or -not (Test-Path -LiteralPath $wscript)) { throw 'Installed runtime is missing.' }
|
|
42
|
-
$definition = $scheduler.NewTask(0)
|
|
43
|
-
$definition.RegistrationInfo.Description = 'Start the Code Relax desktop service when this user signs in.'
|
|
44
|
-
$definition.RegistrationInfo.Source = $taskSource
|
|
45
|
-
$definition.Principal.UserId = $currentUser
|
|
46
|
-
$definition.Principal.LogonType = 3 # TASK_LOGON_INTERACTIVE_TOKEN
|
|
47
|
-
$definition.Principal.RunLevel = 0 # TASK_RUNLEVEL_LUA
|
|
48
|
-
$definition.Settings.Enabled = $true
|
|
49
|
-
$definition.Settings.Hidden = $true
|
|
50
|
-
$definition.Settings.StartWhenAvailable = $true
|
|
51
|
-
$definition.Settings.DisallowStartIfOnBatteries = $false
|
|
52
|
-
$definition.Settings.StopIfGoingOnBatteries = $false
|
|
53
|
-
$definition.Settings.ExecutionTimeLimit = 'PT5M'
|
|
54
|
-
$definition.Settings.MultipleInstances = 2 # TASK_INSTANCES_IGNORE_NEW
|
|
55
|
-
$trigger = $definition.Triggers.Create(9) # TASK_TRIGGER_LOGON
|
|
56
|
-
$trigger.UserId = $currentUser
|
|
57
|
-
$trigger.Enabled = $true
|
|
58
|
-
$exec = $definition.Actions.Create(0) # TASK_ACTION_EXEC
|
|
59
|
-
$exec.Path = $wscript
|
|
60
|
-
$exec.Arguments = $arguments
|
|
61
|
-
$exec.WorkingDirectory = $env:USERPROFILE
|
|
62
|
-
[void]$root.RegisterTaskDefinition($taskName, $definition, 6, $null, $null, 3, $null) # TASK_CREATE_OR_UPDATE
|
|
63
|
-
if ($legacyRegistered.Count -gt 0) { $root.DeleteTask($legacyTaskName, 0) }
|
|
64
|
-
} elseif ($Action -eq 'disable') {
|
|
65
|
-
if ($registered.Count -gt 0) { $root.DeleteTask($taskName, 0) }
|
|
66
|
-
if ($legacyRegistered.Count -gt 0) { $root.DeleteTask($legacyTaskName, 0) }
|
|
67
|
-
}
|
|
68
|
-
if ($Action -ne 'status' -and $legacyExists) { Remove-Item -LiteralPath $legacyPath }
|
|
69
|
-
|
|
70
|
-
$active = @($root.GetTasks(1) | Where-Object { $_.Name -eq $taskName } | Select-Object -First 1)
|
|
71
|
-
$taskEnabled = $active.Count -gt 0 -and $active[0].Enabled
|
|
72
|
-
$taskCurrent = $false
|
|
73
|
-
if ($taskEnabled) {
|
|
74
|
-
$taskAction = $active[0].Definition.Actions.Item(1)
|
|
75
|
-
$taskCurrent = $taskAction.Path -ieq $wscript -and $taskAction.Arguments -ieq $arguments
|
|
76
|
-
}
|
|
77
|
-
@{
|
|
78
|
-
enabled=($taskCurrent -and $legacyRegistered.Count -eq 0)
|
|
79
|
-
taskPresent=$taskEnabled
|
|
80
|
-
path=$(if ($taskEnabled) { 'Task Scheduler:' + $taskPath } elseif ($legacyRegistered.Count -gt 0) { 'Task Scheduler:\' + $legacyTaskName } elseif ($legacyExists) { $legacyPath } else { 'Task Scheduler:' + $taskPath })
|
|
81
|
-
scope='current-user-logon'
|
|
82
|
-
mechanism=$(if ($taskCurrent -and $legacyRegistered.Count -eq 0) { 'task-scheduler' } elseif ($taskEnabled -or $legacyRegistered.Count -gt 0) { 'stale-task-scheduler' } elseif ($legacyExists) { 'legacy-startup-shortcut' } else { 'task-scheduler' })
|
|
83
|
-
} | ConvertTo-Json -Compress
|
|
1
|
+
param([string]$NodePath, [string]$Entry, [ValidateSet('enable','disable','status')][string]$Action='status')
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
4
|
+
|
|
5
|
+
$taskName = 'Code Relax 桌面服务'
|
|
6
|
+
$legacyTaskName = 'Code Relax Bridge'
|
|
7
|
+
$taskSource = '@walkhi/code-relax'
|
|
8
|
+
$taskPath = '\' + $taskName
|
|
9
|
+
$launcher = Join-Path $PSScriptRoot 'resident-start.js'
|
|
10
|
+
$wscript = Join-Path $env:WINDIR 'System32\wscript.exe'
|
|
11
|
+
$arguments = '//B //Nologo "' + $launcher + '" "' + $NodePath + '" "' + $Entry + '"'
|
|
12
|
+
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
13
|
+
|
|
14
|
+
$scheduler = New-Object -ComObject 'Schedule.Service'
|
|
15
|
+
$scheduler.Connect()
|
|
16
|
+
$root = $scheduler.GetFolder('\')
|
|
17
|
+
$registered = @($root.GetTasks(1) | Where-Object { $_.Name -eq $taskName } | Select-Object -First 1)
|
|
18
|
+
$legacyRegistered = @($root.GetTasks(1) | Where-Object { $_.Name -eq $legacyTaskName } | Select-Object -First 1)
|
|
19
|
+
if ($registered.Count -gt 0 -and $registered[0].Definition.RegistrationInfo.Source -ne $taskSource) {
|
|
20
|
+
throw 'Scheduled task is not owned by this installation.'
|
|
21
|
+
}
|
|
22
|
+
if ($legacyRegistered.Count -gt 0 -and $legacyRegistered[0].Definition.RegistrationInfo.Source -ne $taskSource) {
|
|
23
|
+
throw 'Legacy scheduled task is not owned by this installation.'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Remove only the legacy shortcut created by this package. A Startup LNK that
|
|
27
|
+
# launches hidden PowerShell with ExecutionPolicy Bypass triggers AV heuristics.
|
|
28
|
+
$shell = New-Object -ComObject WScript.Shell
|
|
29
|
+
$legacyPath = Join-Path $shell.SpecialFolders.Item('Startup') 'Code Relax Bridge.lnk'
|
|
30
|
+
$legacyLauncher = Join-Path $PSScriptRoot 'resident-start.ps1'
|
|
31
|
+
$legacyExists = Test-Path -LiteralPath $legacyPath
|
|
32
|
+
if ($Action -ne 'status' -and $legacyExists) {
|
|
33
|
+
$legacy = $shell.CreateShortcut($legacyPath)
|
|
34
|
+
if (-not $legacy.Arguments.Contains('"' + $legacyLauncher + '"')) {
|
|
35
|
+
throw 'Legacy startup shortcut is not owned by this installation.'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ($Action -eq 'enable') {
|
|
40
|
+
if (-not (Test-Path -LiteralPath $NodePath) -or -not (Test-Path -LiteralPath $Entry) -or
|
|
41
|
+
-not (Test-Path -LiteralPath $launcher) -or -not (Test-Path -LiteralPath $wscript)) { throw 'Installed runtime is missing.' }
|
|
42
|
+
$definition = $scheduler.NewTask(0)
|
|
43
|
+
$definition.RegistrationInfo.Description = 'Start the Code Relax desktop service when this user signs in.'
|
|
44
|
+
$definition.RegistrationInfo.Source = $taskSource
|
|
45
|
+
$definition.Principal.UserId = $currentUser
|
|
46
|
+
$definition.Principal.LogonType = 3 # TASK_LOGON_INTERACTIVE_TOKEN
|
|
47
|
+
$definition.Principal.RunLevel = 0 # TASK_RUNLEVEL_LUA
|
|
48
|
+
$definition.Settings.Enabled = $true
|
|
49
|
+
$definition.Settings.Hidden = $true
|
|
50
|
+
$definition.Settings.StartWhenAvailable = $true
|
|
51
|
+
$definition.Settings.DisallowStartIfOnBatteries = $false
|
|
52
|
+
$definition.Settings.StopIfGoingOnBatteries = $false
|
|
53
|
+
$definition.Settings.ExecutionTimeLimit = 'PT5M'
|
|
54
|
+
$definition.Settings.MultipleInstances = 2 # TASK_INSTANCES_IGNORE_NEW
|
|
55
|
+
$trigger = $definition.Triggers.Create(9) # TASK_TRIGGER_LOGON
|
|
56
|
+
$trigger.UserId = $currentUser
|
|
57
|
+
$trigger.Enabled = $true
|
|
58
|
+
$exec = $definition.Actions.Create(0) # TASK_ACTION_EXEC
|
|
59
|
+
$exec.Path = $wscript
|
|
60
|
+
$exec.Arguments = $arguments
|
|
61
|
+
$exec.WorkingDirectory = $env:USERPROFILE
|
|
62
|
+
[void]$root.RegisterTaskDefinition($taskName, $definition, 6, $null, $null, 3, $null) # TASK_CREATE_OR_UPDATE
|
|
63
|
+
if ($legacyRegistered.Count -gt 0) { $root.DeleteTask($legacyTaskName, 0) }
|
|
64
|
+
} elseif ($Action -eq 'disable') {
|
|
65
|
+
if ($registered.Count -gt 0) { $root.DeleteTask($taskName, 0) }
|
|
66
|
+
if ($legacyRegistered.Count -gt 0) { $root.DeleteTask($legacyTaskName, 0) }
|
|
67
|
+
}
|
|
68
|
+
if ($Action -ne 'status' -and $legacyExists) { Remove-Item -LiteralPath $legacyPath }
|
|
69
|
+
|
|
70
|
+
$active = @($root.GetTasks(1) | Where-Object { $_.Name -eq $taskName } | Select-Object -First 1)
|
|
71
|
+
$taskEnabled = $active.Count -gt 0 -and $active[0].Enabled
|
|
72
|
+
$taskCurrent = $false
|
|
73
|
+
if ($taskEnabled) {
|
|
74
|
+
$taskAction = $active[0].Definition.Actions.Item(1)
|
|
75
|
+
$taskCurrent = $taskAction.Path -ieq $wscript -and $taskAction.Arguments -ieq $arguments
|
|
76
|
+
}
|
|
77
|
+
@{
|
|
78
|
+
enabled=($taskCurrent -and $legacyRegistered.Count -eq 0)
|
|
79
|
+
taskPresent=$taskEnabled
|
|
80
|
+
path=$(if ($taskEnabled) { 'Task Scheduler:' + $taskPath } elseif ($legacyRegistered.Count -gt 0) { 'Task Scheduler:\' + $legacyTaskName } elseif ($legacyExists) { $legacyPath } else { 'Task Scheduler:' + $taskPath })
|
|
81
|
+
scope='current-user-logon'
|
|
82
|
+
mechanism=$(if ($taskCurrent -and $legacyRegistered.Count -eq 0) { 'task-scheduler' } elseif ($taskEnabled -or $legacyRegistered.Count -gt 0) { 'stale-task-scheduler' } elseif ($legacyExists) { 'legacy-startup-shortcut' } else { 'task-scheduler' })
|
|
83
|
+
} | ConvertTo-Json -Compress
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
param([Parameter(Mandatory=$true)][string]$StateRoot)
|
|
2
|
-
$ErrorActionPreference='Stop'
|
|
3
|
-
[Console]::OutputEncoding=New-Object Text.UTF8Encoding($false)
|
|
4
|
-
Add-Type -Path (Join-Path $PSScriptRoot 'IsolatedProcess.cs')
|
|
5
|
-
New-Item -ItemType Directory -Path $StateRoot -Force | Out-Null
|
|
6
|
-
$anchor=Join-Path $StateRoot 'bridge-config.json'
|
|
7
|
-
$temporary=$false
|
|
8
|
-
if(-not(Test-Path -LiteralPath $anchor)) {
|
|
9
|
-
$anchor=Join-Path $StateRoot ('.state-path-'+[Guid]::NewGuid().ToString()+'.tmp')
|
|
10
|
-
Set-Content -LiteralPath $anchor -Value 'path-resolution' -Encoding ASCII
|
|
11
|
-
$temporary=$true
|
|
12
|
-
}
|
|
13
|
-
try { [IO.Path]::GetDirectoryName([IsolatedProcess]::ResolvePath($anchor)) }
|
|
14
|
-
finally { if($temporary){Remove-Item -LiteralPath $anchor} }
|
|
1
|
+
param([Parameter(Mandatory=$true)][string]$StateRoot)
|
|
2
|
+
$ErrorActionPreference='Stop'
|
|
3
|
+
[Console]::OutputEncoding=New-Object Text.UTF8Encoding($false)
|
|
4
|
+
Add-Type -Path (Join-Path $PSScriptRoot 'IsolatedProcess.cs')
|
|
5
|
+
New-Item -ItemType Directory -Path $StateRoot -Force | Out-Null
|
|
6
|
+
$anchor=Join-Path $StateRoot 'bridge-config.json'
|
|
7
|
+
$temporary=$false
|
|
8
|
+
if(-not(Test-Path -LiteralPath $anchor)) {
|
|
9
|
+
$anchor=Join-Path $StateRoot ('.state-path-'+[Guid]::NewGuid().ToString()+'.tmp')
|
|
10
|
+
Set-Content -LiteralPath $anchor -Value 'path-resolution' -Encoding ASCII
|
|
11
|
+
$temporary=$true
|
|
12
|
+
}
|
|
13
|
+
try { [IO.Path]::GetDirectoryName([IsolatedProcess]::ResolvePath($anchor)) }
|
|
14
|
+
finally { if($temporary){Remove-Item -LiteralPath $anchor} }
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
|
|
5
|
-
// Windows host data locations. Keep existing paths and historical attachments.
|
|
6
|
-
export function runtimePaths(environment = process.env, home = os.homedir()) {
|
|
7
|
-
const codexDataRoot = environment.CODEX_HOME?.trim() || path.join(home, '.codex');
|
|
8
|
-
const serviceRoot = path.join(environment.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'Programs', 'CodexRemote', 'service');
|
|
9
|
-
let installed;
|
|
10
|
-
try { installed = JSON.parse(fs.readFileSync(path.join(serviceRoot, 'installation.json'), 'utf8').replace(/^\uFEFF/, '')); }
|
|
11
|
-
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
12
|
-
return {
|
|
13
|
-
codexDataRoot,
|
|
14
|
-
sessionsRoot: path.join(codexDataRoot, 'sessions'),
|
|
15
|
-
stateRoot: installed?.stateRoot || path.join(environment.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'CodexRemote'),
|
|
16
|
-
serviceRoot,
|
|
17
|
-
uploadsRoot: path.join(environment.ProgramData || 'C:\\ProgramData', 'CodexRemote', 'attachments'),
|
|
18
|
-
credentialsRoot: path.join(environment.ProgramData || 'C:\\ProgramData', 'CodexRemote', 'credentials'),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
// Windows host data locations. Keep existing paths and historical attachments.
|
|
6
|
+
export function runtimePaths(environment = process.env, home = os.homedir()) {
|
|
7
|
+
const codexDataRoot = environment.CODEX_HOME?.trim() || path.join(home, '.codex');
|
|
8
|
+
const serviceRoot = path.join(environment.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'Programs', 'CodexRemote', 'service');
|
|
9
|
+
let installed;
|
|
10
|
+
try { installed = JSON.parse(fs.readFileSync(path.join(serviceRoot, 'installation.json'), 'utf8').replace(/^\uFEFF/, '')); }
|
|
11
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
12
|
+
return {
|
|
13
|
+
codexDataRoot,
|
|
14
|
+
sessionsRoot: path.join(codexDataRoot, 'sessions'),
|
|
15
|
+
stateRoot: installed?.stateRoot || path.join(environment.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'CodexRemote'),
|
|
16
|
+
serviceRoot,
|
|
17
|
+
uploadsRoot: path.join(environment.ProgramData || 'C:\\ProgramData', 'CodexRemote', 'attachments'),
|
|
18
|
+
credentialsRoot: path.join(environment.ProgramData || 'C:\\ProgramData', 'CodexRemote', 'credentials'),
|
|
19
|
+
};
|
|
20
|
+
}
|