@jingyi0605/codingns4dsh 0.1.2 → 0.1.7-rc.3
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.en.md +4 -4
- package/README.md +14 -5
- package/data/build/dist/client/account-bar.js +57 -3
- package/data/build/dist/client/bundle.js +36246 -268
- package/data/build/dist/client/cli-slots.js +60 -18
- package/data/build/dist/client/dsh-h5-bootstrap.d.ts +2 -0
- package/data/build/dist/client/dsh-h5-bootstrap.js +79 -13
- package/data/build/dist/client/features/file-management-panel.d.ts +5 -0
- package/data/build/dist/client/features/file-management-panel.js +48 -0
- package/data/build/dist/client/features/file-management.d.ts +4 -0
- package/data/build/dist/client/features/file-management.js +50 -0
- package/data/build/dist/client/features/index.d.ts +10 -1
- package/data/build/dist/client/features/index.js +12 -1
- package/data/build/dist/client/features/login-protection-panel.js +1 -1
- package/data/build/dist/client/features/login-protection-session.d.ts +2 -0
- package/data/build/dist/client/features/login-protection-session.js +18 -0
- package/data/build/dist/client/features/peer-host.d.ts +4 -0
- package/data/build/dist/client/features/peer-host.js +89 -0
- package/data/build/dist/client/features/reverse-proxy-panel.js +23 -7
- package/data/build/dist/client/features/reverse-proxy.d.ts +1 -2
- package/data/build/dist/client/features/reverse-proxy.js +75 -5
- package/data/build/dist/client/features/types.d.ts +9 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +10 -0
- package/data/build/dist/client/features/workspace-session-enhancement.js +18 -1
- package/data/build/dist/client/file-management-dom.d.ts +12 -0
- package/data/build/dist/client/file-management-dom.js +527 -0
- package/data/build/dist/client/git-management.js +24 -5
- package/data/build/dist/client/host-navigation.d.ts +29 -0
- package/data/build/dist/client/host-navigation.js +29 -0
- package/data/build/dist/client/host-router.d.ts +3 -0
- package/data/build/dist/client/host-router.js +2 -0
- package/data/build/dist/client/index.d.ts +6 -1
- package/data/build/dist/client/index.js +30 -10
- package/data/build/dist/client/locale.js +36 -7
- package/data/build/dist/client/peer-host-connection-button.d.ts +13 -0
- package/data/build/dist/client/peer-host-connection-button.js +69 -0
- package/data/build/dist/client/peer-host-management-api.d.ts +40 -0
- package/data/build/dist/client/peer-host-management-api.js +33 -0
- package/data/build/dist/client/peer-host-management-panel.d.ts +13 -0
- package/data/build/dist/client/peer-host-management-panel.js +181 -0
- package/data/build/dist/client/peer-host-native-session-ui.d.ts +60 -0
- package/data/build/dist/client/peer-host-native-session-ui.js +372 -0
- package/data/build/dist/client/peer-host-scoped-client.d.ts +67 -0
- package/data/build/dist/client/peer-host-scoped-client.js +316 -0
- package/data/build/dist/client/peer-host-session-controller.d.ts +30 -0
- package/data/build/dist/client/peer-host-session-controller.js +95 -0
- package/data/build/dist/client/quick-phrase-slot.js +14 -0
- package/data/build/dist/client/remote-web-context.js +26 -4
- package/data/build/dist/client/session-changed-files-view.d.ts +15 -0
- package/data/build/dist/client/session-changed-files-view.js +337 -0
- package/data/build/dist/client/settings-bridge.d.ts +14 -4
- package/data/build/dist/client/settings-bridge.js +59 -12
- package/data/build/dist/client/settings-section.js +17 -4
- package/data/build/dist/client/subscription-slot.js +21 -8
- package/data/build/dist/client/workspace-session-rightbar-dom.d.ts +28 -0
- package/data/build/dist/client/workspace-session-rightbar-dom.js +291 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +25 -33
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +7 -1
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +34 -11
- package/data/build/dist/dsh-capabilities/matrix.js +9 -0
- package/data/build/dist/dsh-capabilities/routes.js +35 -0
- package/data/build/dist/dsh-capabilities/settings-store.d.ts +12 -0
- package/data/build/dist/dsh-capabilities/settings-store.js +34 -0
- package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
- package/data/build/dist/features/host-router.d.ts +26 -0
- package/data/build/dist/features/host-router.js +110 -0
- package/data/build/dist/features/registry.js +3 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +13 -1
- package/data/build/dist/host/cli-adapters/codex-driver.js +528 -72
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +21 -2
- package/data/build/dist/host/cli-adapters/command-code-driver.js +408 -105
- package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +47 -28
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +37 -0
- package/data/build/dist/host/cli-adapters/feature.js +94 -10
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +14 -3
- package/data/build/dist/host/cli-adapters/kimi-driver.js +3 -3
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/model-catalog.js +8 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +14 -2
- package/data/build/dist/host/cli-adapters/pi-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/process-utils.d.ts +8 -0
- package/data/build/dist/host/cli-adapters/process-utils.js +48 -0
- package/data/build/dist/host/cli-adapters/registry.d.ts +6 -1
- package/data/build/dist/host/cli-adapters/registry.js +41 -7
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +23 -4
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +36 -15
- package/data/build/dist/host/dsh-device-runtime.d.ts +2 -0
- package/data/build/dist/host/dsh-device-runtime.js +55 -29
- package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
- package/data/build/dist/host/dsh-runtime-version.js +71 -2
- package/data/build/dist/host/features/auth.js +10 -2
- package/data/build/dist/host/features/file-management.d.ts +5 -0
- package/data/build/dist/host/features/file-management.js +267 -0
- package/data/build/dist/host/features/git-management.js +24 -2
- package/data/build/dist/host/features/index.d.ts +2 -0
- package/data/build/dist/host/features/index.js +6 -0
- package/data/build/dist/host/features/peer-host.d.ts +22 -0
- package/data/build/dist/host/features/peer-host.js +322 -0
- package/data/build/dist/host/features/types.d.ts +4 -0
- package/data/build/dist/host/host-router.d.ts +3 -0
- package/data/build/dist/host/host-router.js +2 -0
- package/data/build/dist/host/index-injection.d.ts +17 -0
- package/data/build/dist/host/index-injection.js +24 -0
- package/data/build/dist/host/index.d.ts +4 -1
- package/data/build/dist/host/index.js +41 -7
- package/data/build/dist/host/lan-access-dsh.d.ts +34 -1
- package/data/build/dist/host/lan-access-dsh.js +145 -26
- package/data/build/dist/host/modules/peer-host/host-api-proxy-service.d.ts +52 -0
- package/data/build/dist/host/modules/peer-host/host-api-proxy-service.js +166 -0
- package/data/build/dist/host/modules/peer-host/host-ws-connector.d.ts +31 -0
- package/data/build/dist/host/modules/peer-host/host-ws-connector.js +108 -0
- package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.d.ts +26 -0
- package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.js +159 -0
- package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.d.ts +50 -0
- package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.js +97 -0
- package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.d.ts +19 -0
- package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.js +54 -0
- package/data/build/dist/host/modules/peer-host/peer-host-handshake.d.ts +44 -0
- package/data/build/dist/host/modules/peer-host/peer-host-handshake.js +133 -0
- package/data/build/dist/host/modules/peer-host/peer-host-relay.d.ts +65 -0
- package/data/build/dist/host/modules/peer-host/peer-host-relay.js +196 -0
- package/data/build/dist/host/modules/peer-host/peer-host-session.d.ts +42 -0
- package/data/build/dist/host/modules/peer-host/peer-host-session.js +152 -0
- package/data/build/dist/host/modules/peer-host/peer-host-store.d.ts +92 -0
- package/data/build/dist/host/modules/peer-host/peer-host-store.js +323 -0
- package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.d.ts +34 -0
- package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.js +156 -0
- package/data/build/dist/host/native-session-bridge.d.ts +18 -2
- package/data/build/dist/host/native-session-bridge.js +236 -8
- package/data/build/dist/host/remote-web-runtime.js +26 -1
- package/data/build/dist/host/rpc.js +8 -3
- package/data/build/dist/host/session-changes.d.ts +5 -0
- package/data/build/dist/host/session-changes.js +163 -0
- package/data/build/dist/host/settings.js +6 -0
- package/data/build/dist/index.d.ts +1 -1
- package/data/build/dist/index.js +1 -1
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +23 -2
- package/data/build/dist/shared/contracts/config.d.ts +15 -0
- package/data/build/dist/shared/contracts/config.js +10 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +2 -0
- package/data/build/dist/shared/contracts/errors.d.ts +14 -0
- package/data/build/dist/shared/contracts/errors.js +15 -0
- package/data/build/dist/shared/contracts/feature.d.ts +2 -0
- package/data/build/dist/shared/contracts/file-management.d.ts +6 -0
- package/data/build/dist/shared/contracts/file-management.js +3 -0
- package/data/build/dist/shared/contracts/peer-host.d.ts +116 -15
- package/data/build/dist/shared/contracts/peer-host.js +19 -0
- package/data/build/dist/shared/contracts/version.d.ts +1 -1
- package/data/build/dist/shared/contracts/version.js +1 -1
- package/data/build/dist/shared/index.d.ts +4 -3
- package/data/build/dist/shared/index.js +2 -2
- package/data/build/dist/transport/debug.d.ts +5 -0
- package/data/build/dist/transport/debug.js +23 -1
- package/data/build/dist/transport/dsh-transport.d.ts +2 -0
- package/data/build/dist/transport/dsh-transport.js +11 -0
- package/data/build/dist/transport/webrtc-client.d.ts +2 -0
- package/data/build/dist/transport/webrtc-client.js +2 -1
- package/data/build/dist/transport/webrtc-host.d.ts +2 -0
- package/data/build/dist/transport/webrtc-host.js +6 -3
- package/data/build/dist/typert.host.d.ts +1 -1
- package/data/build/dist/typert.host.js +2 -1
- package/dsh.bundle.patch +10 -0
- package/package.json +19 -11
- package/profile/README.md +2 -2
- package/profile/package.json +2 -2
- package/profile/version.json +1 -1
- package/version.json +1 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from 'node:path';
|
|
3
|
+
import { CodingNsRpcError } from '../rpc-table.js';
|
|
4
|
+
import { readSessionChangedFiles } from '../session-changes.js';
|
|
5
|
+
const MAX_EDIT_BYTES = 4 * 1024 * 1024;
|
|
6
|
+
/** 文件管理增强的 Host 边界;所有路径先解析到已知工作区,再执行文件操作。 */
|
|
7
|
+
export function createFileManagementFeature() {
|
|
8
|
+
return {
|
|
9
|
+
descriptor: {
|
|
10
|
+
name: 'fileManagement',
|
|
11
|
+
version: '0.1.0',
|
|
12
|
+
enabledByDefault: false,
|
|
13
|
+
dependencies: [],
|
|
14
|
+
runtime: 'host',
|
|
15
|
+
},
|
|
16
|
+
start(context) {
|
|
17
|
+
context.resources.add(context.services.rpc.register('fileManagement', async (action, payload) => {
|
|
18
|
+
const input = record(payload);
|
|
19
|
+
switch (action) {
|
|
20
|
+
case 'session-changes': {
|
|
21
|
+
const sessionId = requiredString(input.sessionId, 'sessionId');
|
|
22
|
+
const workspaceId = typeof input.workspaceId === 'string' ? input.workspaceId.trim() : '';
|
|
23
|
+
const root = workspaceId !== ''
|
|
24
|
+
? context.services.resolveWorkspaceRoot?.(workspaceId) ?? findSessionRoot(context.services, sessionId)
|
|
25
|
+
: findSessionRoot(context.services, sessionId);
|
|
26
|
+
if (root === null || root === undefined || root.trim() === '') {
|
|
27
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_WORKSPACE_UNAVAILABLE', '当前会话没有可用的工作区目录');
|
|
28
|
+
}
|
|
29
|
+
return readSessionChangedFiles(context.services, sessionId, root);
|
|
30
|
+
}
|
|
31
|
+
case 'read': {
|
|
32
|
+
const target = resolveTarget(context.services, input);
|
|
33
|
+
const info = await stat(target.absolute);
|
|
34
|
+
if (!info.isFile())
|
|
35
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_NOT_FILE', '目标不是普通文件');
|
|
36
|
+
if (typeof info.size === 'number' && info.size > MAX_EDIT_BYTES)
|
|
37
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_TOO_LARGE', '文件超过可编辑大小限制');
|
|
38
|
+
return { path: target.absolute, content: await readFile(target.absolute, 'utf8') };
|
|
39
|
+
}
|
|
40
|
+
case 'download': {
|
|
41
|
+
const target = resolveTarget(context.services, input);
|
|
42
|
+
const info = await stat(target.absolute);
|
|
43
|
+
if (!info.isFile())
|
|
44
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_NOT_FILE', '目标不是普通文件');
|
|
45
|
+
const content = await readFile(target.absolute);
|
|
46
|
+
return {
|
|
47
|
+
path: target.absolute,
|
|
48
|
+
fileName: basename(target.absolute),
|
|
49
|
+
mimeType: mimeTypeForPath(target.absolute),
|
|
50
|
+
contentBase64: Buffer.from(content).toString('base64'),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
case 'write': {
|
|
54
|
+
const target = resolveTarget(context.services, input);
|
|
55
|
+
const content = requiredString(input.content, 'content', false);
|
|
56
|
+
if (Buffer.byteLength(content, 'utf8') > MAX_EDIT_BYTES)
|
|
57
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_TOO_LARGE', '文件超过可编辑大小限制');
|
|
58
|
+
const info = await stat(target.absolute);
|
|
59
|
+
if (!info.isFile())
|
|
60
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_NOT_FILE', '目标不是普通文件');
|
|
61
|
+
await atomicWrite(target.absolute, content);
|
|
62
|
+
return { path: target.absolute };
|
|
63
|
+
}
|
|
64
|
+
case 'create-file': {
|
|
65
|
+
const target = resolveTarget(context.services, input);
|
|
66
|
+
await mkdir(dirname(target.absolute), { recursive: true });
|
|
67
|
+
await writeFile(target.absolute, '', { encoding: 'utf8', flag: 'wx' });
|
|
68
|
+
return { path: target.absolute };
|
|
69
|
+
}
|
|
70
|
+
case 'create-directory': {
|
|
71
|
+
const target = resolveTarget(context.services, input);
|
|
72
|
+
await mkdir(target.absolute, { recursive: false });
|
|
73
|
+
return { path: target.absolute };
|
|
74
|
+
}
|
|
75
|
+
case 'rename': {
|
|
76
|
+
const source = resolveTarget(context.services, input);
|
|
77
|
+
const destination = resolveTarget(context.services, { ...input, path: input.destination });
|
|
78
|
+
await mkdir(dirname(destination.absolute), { recursive: true });
|
|
79
|
+
await rename(source.absolute, destination.absolute);
|
|
80
|
+
return { from: source.absolute, path: destination.absolute };
|
|
81
|
+
}
|
|
82
|
+
case 'copy':
|
|
83
|
+
return copyTargets(context.services, input);
|
|
84
|
+
case 'move':
|
|
85
|
+
return moveTargets(context.services, input);
|
|
86
|
+
case 'delete': {
|
|
87
|
+
const paths = requiredStringArray(input.paths, 'paths');
|
|
88
|
+
for (const path of paths) {
|
|
89
|
+
const target = resolveTarget(context.services, { ...input, path });
|
|
90
|
+
await rm(target.absolute, { recursive: true, force: false });
|
|
91
|
+
}
|
|
92
|
+
return { paths };
|
|
93
|
+
}
|
|
94
|
+
case 'git-ignore':
|
|
95
|
+
return addGitIgnore(context.services, input);
|
|
96
|
+
default:
|
|
97
|
+
throw new CodingNsRpcError('CODINGNS_RPC_NOT_FOUND', `未知文件管理 RPC: fileManagement/${action}`);
|
|
98
|
+
}
|
|
99
|
+
}));
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function copyTargets(services, input) {
|
|
104
|
+
const paths = requiredStringArray(input.paths, 'paths');
|
|
105
|
+
const destination = resolveTarget(services, { ...input, path: input.destination });
|
|
106
|
+
const destinationInfo = await stat(destination.absolute);
|
|
107
|
+
if (!destinationInfo.isDirectory())
|
|
108
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_NOT_DIRECTORY', '粘贴目标不是目录');
|
|
109
|
+
for (const path of paths) {
|
|
110
|
+
const source = resolveTarget(services, { ...input, path });
|
|
111
|
+
await copyEntry(source.absolute, join(destination.absolute, basename(source.absolute)));
|
|
112
|
+
}
|
|
113
|
+
return { paths };
|
|
114
|
+
}
|
|
115
|
+
async function moveTargets(services, input) {
|
|
116
|
+
const paths = requiredStringArray(input.paths, 'paths');
|
|
117
|
+
const destination = resolveTarget(services, { ...input, path: input.destination });
|
|
118
|
+
const destinationInfo = await stat(destination.absolute);
|
|
119
|
+
if (!destinationInfo.isDirectory())
|
|
120
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_NOT_DIRECTORY', '粘贴目标不是目录');
|
|
121
|
+
for (const path of paths) {
|
|
122
|
+
const source = resolveTarget(services, { ...input, path });
|
|
123
|
+
await rename(source.absolute, join(destination.absolute, basename(source.absolute)));
|
|
124
|
+
}
|
|
125
|
+
return { paths };
|
|
126
|
+
}
|
|
127
|
+
async function copyEntry(source, destination) {
|
|
128
|
+
const info = await stat(source);
|
|
129
|
+
if (info.isDirectory()) {
|
|
130
|
+
await mkdir(destination, { recursive: false });
|
|
131
|
+
for (const entry of await readdir(source, { withFileTypes: true })) {
|
|
132
|
+
await copyEntry(join(source, entry.name), join(destination, entry.name));
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await copyFile(source, destination);
|
|
137
|
+
}
|
|
138
|
+
async function addGitIgnore(services, input) {
|
|
139
|
+
const paths = requiredStringArray(input.paths, 'paths');
|
|
140
|
+
const resolved = paths.map((path) => resolveTarget(services, { ...input, path }));
|
|
141
|
+
const roots = new Set(resolved.map((item) => item.root));
|
|
142
|
+
if (roots.size !== 1)
|
|
143
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_MULTIPLE_ROOTS', '一次只能处理同一个工作区中的文件');
|
|
144
|
+
const root = resolved[0]?.root;
|
|
145
|
+
if (root === undefined)
|
|
146
|
+
throw new TypeError('paths 不能为空');
|
|
147
|
+
const ignorePath = join(root, '.gitignore');
|
|
148
|
+
let current = '';
|
|
149
|
+
try {
|
|
150
|
+
current = await readFile(ignorePath, 'utf8');
|
|
151
|
+
}
|
|
152
|
+
catch { /* .gitignore 不存在时从空文件开始。 */ }
|
|
153
|
+
const lines = current.split(/\r?\n/u).filter((line) => line.trim() !== '');
|
|
154
|
+
for (const item of resolved) {
|
|
155
|
+
const path = relative(root, item.absolute).replaceAll('\\', '/');
|
|
156
|
+
const info = await stat(item.absolute);
|
|
157
|
+
const pattern = info.isDirectory() ? `${path}/` : path;
|
|
158
|
+
if (!lines.includes(pattern))
|
|
159
|
+
lines.push(pattern);
|
|
160
|
+
}
|
|
161
|
+
await atomicWrite(ignorePath, lines.length === 0 ? '' : `${lines.join('\n')}\n`);
|
|
162
|
+
return { paths };
|
|
163
|
+
}
|
|
164
|
+
function resolveTarget(services, input) {
|
|
165
|
+
const rawPath = requiredString(input.path, 'path');
|
|
166
|
+
const sessionId = typeof input.sessionId === 'string' ? input.sessionId.trim() : '';
|
|
167
|
+
const sessionRoot = sessionId === '' ? undefined : findSessionRoot(services, sessionId);
|
|
168
|
+
const absolute = isAbsolute(rawPath)
|
|
169
|
+
? normalize(rawPath)
|
|
170
|
+
: sessionRoot === undefined ? resolve(rawPath) : normalize(join(sessionRoot, rawPath));
|
|
171
|
+
const roots = [...new Set([...(services.listWorkspaceRoots?.() ?? []), ...(sessionRoot === undefined ? [] : [sessionRoot])])]
|
|
172
|
+
.map((root) => normalize(root))
|
|
173
|
+
.filter((root) => root !== '')
|
|
174
|
+
.sort((left, right) => right.length - left.length);
|
|
175
|
+
const root = roots.find((candidate) => isInside(candidate, absolute));
|
|
176
|
+
if (root === undefined)
|
|
177
|
+
throw new CodingNsRpcError('FILE_MANAGEMENT_OUTSIDE_WORKSPACE', '路径不在已知工作区内');
|
|
178
|
+
return { absolute, root };
|
|
179
|
+
}
|
|
180
|
+
function findSessionRoot(services, sessionId) {
|
|
181
|
+
const session = services.nativeSessions?.get(sessionId);
|
|
182
|
+
return findCwd(session, 0);
|
|
183
|
+
}
|
|
184
|
+
function findCwd(value, depth) {
|
|
185
|
+
if (depth > 5 || value === null || typeof value !== 'object')
|
|
186
|
+
return undefined;
|
|
187
|
+
if (Array.isArray(value)) {
|
|
188
|
+
for (const item of value) {
|
|
189
|
+
const found = findCwd(item, depth + 1);
|
|
190
|
+
if (found !== undefined)
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
const record = value;
|
|
196
|
+
for (const key of ['cwd', 'workingDirectory']) {
|
|
197
|
+
if (typeof record[key] === 'string' && record[key].trim() !== '')
|
|
198
|
+
return record[key].trim();
|
|
199
|
+
}
|
|
200
|
+
for (const key of ['header', 'request', 'context', 'data', 'meta', 'session']) {
|
|
201
|
+
const found = findCwd(record[key], depth + 1);
|
|
202
|
+
if (found !== undefined)
|
|
203
|
+
return found;
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
function isInside(root, target) {
|
|
208
|
+
const escaped = relative(root, target);
|
|
209
|
+
return escaped === '' || (!escaped.startsWith('..') && !isAbsolute(escaped));
|
|
210
|
+
}
|
|
211
|
+
async function atomicWrite(path, content) {
|
|
212
|
+
const temporary = `${path}.codingns-tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
213
|
+
let mode = 0o600;
|
|
214
|
+
try {
|
|
215
|
+
const current = await stat(path);
|
|
216
|
+
mode = current.mode === undefined ? mode : current.mode & 0o777;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// 新建 .gitignore 时使用受限默认权限。
|
|
220
|
+
}
|
|
221
|
+
await writeFile(temporary, content, { encoding: 'utf8', mode });
|
|
222
|
+
try {
|
|
223
|
+
await rename(temporary, path);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function requiredString(value, field, trim = true) {
|
|
231
|
+
if (typeof value !== 'string' || (trim && value.trim() === ''))
|
|
232
|
+
throw new TypeError(`${field} 必须是非空字符串`);
|
|
233
|
+
return trim ? value.trim() : value;
|
|
234
|
+
}
|
|
235
|
+
function requiredStringArray(value, field) {
|
|
236
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== 'string' || item.trim() === ''))
|
|
237
|
+
throw new TypeError(`${field} 必须是非空字符串数组`);
|
|
238
|
+
return value.map((item) => item.trim());
|
|
239
|
+
}
|
|
240
|
+
function record(value) {
|
|
241
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
242
|
+
throw new TypeError('文件管理 RPC 参数必须是对象');
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
function mimeTypeForPath(path) {
|
|
246
|
+
const extension = path.toLowerCase().match(/\.([a-z0-9]+)$/u)?.[1] ?? '';
|
|
247
|
+
return {
|
|
248
|
+
css: 'text/css',
|
|
249
|
+
csv: 'text/csv',
|
|
250
|
+
gif: 'image/gif',
|
|
251
|
+
html: 'text/html',
|
|
252
|
+
jpg: 'image/jpeg',
|
|
253
|
+
jpeg: 'image/jpeg',
|
|
254
|
+
js: 'text/javascript',
|
|
255
|
+
json: 'application/json',
|
|
256
|
+
md: 'text/markdown',
|
|
257
|
+
pdf: 'application/pdf',
|
|
258
|
+
png: 'image/png',
|
|
259
|
+
svg: 'image/svg+xml',
|
|
260
|
+
txt: 'text/plain',
|
|
261
|
+
xml: 'application/xml',
|
|
262
|
+
yaml: 'application/yaml',
|
|
263
|
+
yml: 'application/yaml',
|
|
264
|
+
zip: 'application/zip',
|
|
265
|
+
}[extension] ?? 'application/octet-stream';
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=file-management.js.map
|
|
@@ -105,6 +105,17 @@ async function readDiff(workspaceId, root, target, staged) {
|
|
|
105
105
|
const path = safeTarget(root, target);
|
|
106
106
|
try {
|
|
107
107
|
const result = await runGit(root, [...(staged ? ['diff', '--cached'] : ['diff']), '--binary', '--', path]);
|
|
108
|
+
if (!staged && result.stdout === '' && await isUntracked(root, path)) {
|
|
109
|
+
try {
|
|
110
|
+
await runGit(root, ['diff', '--no-index', '--binary', '--', process.platform === 'win32' ? 'NUL' : '/dev/null', path]);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof GitCommandError && error.stdout !== '') {
|
|
114
|
+
const content = error.stdout.slice(0, MAX_DIFF_BYTES);
|
|
115
|
+
return { workspaceId, path: normalizeGitPath(target), staged, binary: /Binary files /u.test(error.stdout), truncated: error.stdout.length > content.length, content };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
108
119
|
const content = result.stdout.slice(0, MAX_DIFF_BYTES);
|
|
109
120
|
return { workspaceId, path: normalizeGitPath(target), staged, binary: /Binary files /u.test(result.stdout), truncated: result.stdout.length > content.length, content };
|
|
110
121
|
}
|
|
@@ -114,6 +125,15 @@ async function readDiff(workspaceId, root, target, staged) {
|
|
|
114
125
|
return { workspaceId, path: normalizeGitPath(target), staged, binary: false, truncated: false, content: '' };
|
|
115
126
|
}
|
|
116
127
|
}
|
|
128
|
+
async function isUntracked(root, path) {
|
|
129
|
+
try {
|
|
130
|
+
await runGit(root, ['ls-files', '--error-unmatch', '--', path]);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
117
137
|
async function mutateTargets(workspaceId, root, rawTargets, action) {
|
|
118
138
|
const targets = safeTargets(root, rawTargets);
|
|
119
139
|
if (targets.length > 0)
|
|
@@ -239,16 +259,18 @@ async function runGit(cwd, args) {
|
|
|
239
259
|
}
|
|
240
260
|
catch (error) {
|
|
241
261
|
const detail = error;
|
|
242
|
-
throw new GitCommandError(`git ${args.join(' ')} 执行失败`, detail.stderr ?? detail.stdout ?? String(error), detail.code);
|
|
262
|
+
throw new GitCommandError(`git ${args.join(' ')} 执行失败`, detail.stderr ?? detail.stdout ?? String(error), detail.code, detail.stdout ?? '');
|
|
243
263
|
}
|
|
244
264
|
}
|
|
245
265
|
class GitCommandError extends Error {
|
|
246
266
|
stderr;
|
|
247
267
|
commandCode;
|
|
248
|
-
|
|
268
|
+
stdout;
|
|
269
|
+
constructor(message, stderr, commandCode, stdout = '') {
|
|
249
270
|
super(message);
|
|
250
271
|
this.stderr = stderr;
|
|
251
272
|
this.commandCode = commandCode;
|
|
273
|
+
this.stdout = stdout;
|
|
252
274
|
}
|
|
253
275
|
}
|
|
254
276
|
function safeTargets(root, value) {
|
|
@@ -16,5 +16,7 @@ export { createTerminalProcessFeature } from './terminal-process.js';
|
|
|
16
16
|
export { createDebugFeature } from './debug.js';
|
|
17
17
|
export { createHostStatusFeature } from './host-status.js';
|
|
18
18
|
export { createGitManagementFeature } from './git-management.js';
|
|
19
|
+
export { createFileManagementFeature } from './file-management.js';
|
|
20
|
+
export { createPeerHostFeature, toPeerHostClientRecord } from './peer-host.js';
|
|
19
21
|
export type { CodingNsHostServices } from './types.js';
|
|
20
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -6,6 +6,8 @@ import { createTerminalProcessFeature } from './terminal-process.js';
|
|
|
6
6
|
import { createDebugFeature } from './debug.js';
|
|
7
7
|
import { createHostStatusFeature } from './host-status.js';
|
|
8
8
|
import { createGitManagementFeature } from './git-management.js';
|
|
9
|
+
import { createFileManagementFeature } from './file-management.js';
|
|
10
|
+
import { createPeerHostFeature } from './peer-host.js';
|
|
9
11
|
/** 生产启动可把实际 controller 模式注入状态模块,避免向设置页报告默认占位值。 */
|
|
10
12
|
export function createHostFeatures(options = {}) {
|
|
11
13
|
return [
|
|
@@ -17,6 +19,8 @@ export function createHostFeatures(options = {}) {
|
|
|
17
19
|
createDebugFeature(),
|
|
18
20
|
createHostStatusFeature(),
|
|
19
21
|
createGitManagementFeature(),
|
|
22
|
+
createFileManagementFeature(),
|
|
23
|
+
createPeerHostFeature(),
|
|
20
24
|
];
|
|
21
25
|
}
|
|
22
26
|
/** Host 侧功能模块清单:新增模块在这里登记一行,不需要改动入口或 RPC 分发。 */
|
|
@@ -29,4 +33,6 @@ export { createTerminalProcessFeature } from './terminal-process.js';
|
|
|
29
33
|
export { createDebugFeature } from './debug.js';
|
|
30
34
|
export { createHostStatusFeature } from './host-status.js';
|
|
31
35
|
export { createGitManagementFeature } from './git-management.js';
|
|
36
|
+
export { createFileManagementFeature } from './file-management.js';
|
|
37
|
+
export { createPeerHostFeature, toPeerHostClientRecord } from './peer-host.js';
|
|
32
38
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FeatureModule } from '../../shared/contracts/feature.js';
|
|
2
|
+
import type { CodingNsHostServices } from './types.js';
|
|
3
|
+
import type { PeerHostRecord, PeerHostClientRecord } from '../../shared/contracts/peer-host.js';
|
|
4
|
+
import type { AggregateHostSource } from '../modules/peer-host/peer-host-aggregate-service.js';
|
|
5
|
+
import { type PeerHostRemoteConnector } from '../modules/peer-host/host-ws-proxy-service.js';
|
|
6
|
+
import { type PeerHostRelayTransportFactory } from '../modules/peer-host/peer-host-relay.js';
|
|
7
|
+
export interface PeerHostFeatureOptions {
|
|
8
|
+
readonly stateDirectory?: string;
|
|
9
|
+
readonly ownerUserId?: string;
|
|
10
|
+
readonly encryptionKey?: Uint8Array;
|
|
11
|
+
readonly fetchImpl?: typeof fetch;
|
|
12
|
+
/** 测试或已验证的 Host-to-Host WebSocket connector;未注入时保持不可用。 */
|
|
13
|
+
readonly connectRemote?: PeerHostRemoteConnector;
|
|
14
|
+
/** 仅允许复用已验证的 Host 侧 Relay Transport;缺省时中转保持不可用。 */
|
|
15
|
+
readonly relayTransport?: PeerHostRelayTransportFactory;
|
|
16
|
+
/** 当前 Host/已验证 PeerHost 的摘要源;未注入时必须保持明确降级。 */
|
|
17
|
+
readonly aggregateSources?: () => Promise<readonly AggregateHostSource[]>;
|
|
18
|
+
}
|
|
19
|
+
/** PeerHost Host 模块;配置、握手和目标登录态只在 Host 进程内装配。 */
|
|
20
|
+
export declare function createPeerHostFeature(options?: PeerHostFeatureOptions): FeatureModule<CodingNsHostServices>;
|
|
21
|
+
export declare function toPeerHostClientRecord(record: PeerHostRecord): PeerHostClientRecord;
|
|
22
|
+
//# sourceMappingURL=peer-host.d.ts.map
|