@orion-agents/orion-code 0.3.15 → 0.3.18
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/CHANGELOG.md +254 -0
- package/dist/runtime/release-receipts.d.ts +15 -3
- package/dist/runtime/release-receipts.js +27 -1
- package/dist/services/session-storage.d.ts +9 -0
- package/dist/services/session-storage.js +27 -1
- package/dist/web/git-compare-service.d.ts +45 -0
- package/dist/web/git-compare-service.js +160 -0
- package/dist/web/git-diff-document.d.ts +83 -0
- package/dist/web/git-diff-document.js +214 -0
- package/dist/web/git-file-source.d.ts +25 -0
- package/dist/web/git-file-source.js +21 -0
- package/dist/web/git-history-service.d.ts +151 -0
- package/dist/web/git-history-service.js +610 -0
- package/dist/web/git-index-transaction.d.ts +45 -0
- package/dist/web/git-index-transaction.js +174 -0
- package/dist/web/git-patch-builder.d.ts +30 -0
- package/dist/web/git-patch-builder.js +138 -0
- package/dist/web/git-read-model-service.d.ts +338 -18
- package/dist/web/git-read-model-service.js +1039 -110
- package/dist/web/git-version-service.d.ts +71 -0
- package/dist/web/git-version-service.js +255 -0
- package/dist/web/native-directory-picker.d.ts +63 -0
- package/dist/web/native-directory-picker.js +169 -0
- package/dist/web/protocol.d.ts +51 -2
- package/dist/web/server.js +384 -8
- package/dist/web/workbench-controller.d.ts +126 -10
- package/dist/web/workbench-controller.js +383 -15
- package/dist/web/workspace-open-telemetry.d.ts +55 -0
- package/dist/web/workspace-open-telemetry.js +73 -0
- package/dist/web-client/assets/FilesPanel-BARQCmne.js +1 -0
- package/dist/web-client/assets/GitPanel-DjB9eSiJ.js +4 -0
- package/dist/web-client/assets/ReviewPanel-YhEGlp-G.js +5 -0
- package/dist/web-client/assets/{TerminalPanel-BpsLphLv.js → TerminalPanel-DnWh3Vp2.js} +1 -1
- package/dist/web-client/assets/{index-D3sxrAh_.css → index--hnGXXjj.css} +1 -1
- package/dist/web-client/assets/index-Drja0_BT.js +20 -0
- package/dist/web-client/assets/{useAutoNotice-BeSf9Dfq.js → useAutoNotice-DfQi4G1X.js} +1 -1
- package/dist/web-client/index.html +2 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/web-client/assets/DiffViewer-C0A42Lma.js +0 -5
- package/dist/web-client/assets/FilesPanel-DiXKnFMj.js +0 -1
- package/dist/web-client/assets/GitPanel--_Yrbldp.js +0 -1
- package/dist/web-client/assets/ReviewPanel-BilzL9Ma.js +0 -1
- package/dist/web-client/assets/index-C2M48tcK.js +0 -20
|
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WebWorkbenchController = exports.WebWorkbenchError = void 0;
|
|
4
4
|
exports.pageItems = pageItems;
|
|
5
5
|
exports.pageCollectionItems = pageCollectionItems;
|
|
6
|
+
exports.hasProjectSignal = hasProjectSignal;
|
|
6
7
|
const crypto_1 = require("crypto");
|
|
7
8
|
const fs_1 = require("fs");
|
|
9
|
+
const promises_1 = require("fs/promises");
|
|
8
10
|
const path_1 = require("path");
|
|
9
11
|
const agent_runtime_controller_1 = require("../runtime/agent-runtime-controller");
|
|
10
12
|
const mcp_1 = require("../runtime/mcp");
|
|
@@ -30,6 +32,8 @@ const file_write_service_1 = require("./file-write-service");
|
|
|
30
32
|
const git_read_model_service_1 = require("./git-read-model-service");
|
|
31
33
|
const workspace_mutation_arbiter_1 = require("./workspace-mutation-arbiter");
|
|
32
34
|
const protocol_1 = require("./protocol");
|
|
35
|
+
const native_directory_picker_1 = require("./native-directory-picker");
|
|
36
|
+
const workspace_open_telemetry_1 = require("./workspace-open-telemetry");
|
|
33
37
|
const review_service_1 = require("./review-service");
|
|
34
38
|
const session_runtime_registry_1 = require("./session-runtime-registry");
|
|
35
39
|
const terminal_manager_1 = require("./terminal-manager");
|
|
@@ -51,6 +55,9 @@ class WebWorkbenchController {
|
|
|
51
55
|
* released only during Host shutdown.
|
|
52
56
|
*/
|
|
53
57
|
this.workspaceKernels = new Map();
|
|
58
|
+
this.pickerInFlight = false;
|
|
59
|
+
/** v0.3.17 — bounded, path-free open-path traces exposed through diagnostics(). */
|
|
60
|
+
this.openTelemetry = new workspace_open_telemetry_1.WorkspaceOpenTelemetryV1();
|
|
54
61
|
this.mutationResults = new Map();
|
|
55
62
|
this.sessionViews = new Map();
|
|
56
63
|
this.sessionViewBytes = 0;
|
|
@@ -86,6 +93,7 @@ class WebWorkbenchController {
|
|
|
86
93
|
onStateChanged: state => this.emitWorkspaceMutationState(state),
|
|
87
94
|
});
|
|
88
95
|
this.workspaceRegistry = options.workspaceRegistry ?? new workspace_registry_1.WorkspaceRegistryV1();
|
|
96
|
+
this.directoryPicker = options.nativeDirectoryPicker ?? (0, native_directory_picker_1.createNativeDirectoryPicker)();
|
|
89
97
|
this.terminalManager = new terminal_manager_1.TerminalManagerV1({
|
|
90
98
|
resolveWorkspace: workspaceId => this.workspaceRegistry.find(workspaceId)?.canonicalPath,
|
|
91
99
|
getActiveContext: () => ({
|
|
@@ -220,6 +228,95 @@ class WebWorkbenchController {
|
|
|
220
228
|
this.assertContextGuard(context);
|
|
221
229
|
return result;
|
|
222
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* v0.3.16 — Ask the Host to run the OS directory picker.
|
|
233
|
+
*
|
|
234
|
+
* This is a *browse* action: it never registers, activates or reads a
|
|
235
|
+
* workspace. Only one picker may be open at a time so a stuck native dialog
|
|
236
|
+
* cannot be stacked, and the result is a plain path the caller must still
|
|
237
|
+
* confirm through the normal activation flow.
|
|
238
|
+
*/
|
|
239
|
+
async pickDirectory(input) {
|
|
240
|
+
if (this.closed) {
|
|
241
|
+
throw new errors_1.WebWorkbenchError(503, 'The local Web Host is shutting down.', 'host_closed');
|
|
242
|
+
}
|
|
243
|
+
if (this.pickerInFlight) {
|
|
244
|
+
throw new errors_1.WebWorkbenchError(429, 'A directory picker is already open.', 'picker_busy');
|
|
245
|
+
}
|
|
246
|
+
const span = input.requestId
|
|
247
|
+
? this.openTelemetry.start(input.requestId, 'pick-directory')
|
|
248
|
+
: undefined;
|
|
249
|
+
this.pickerInFlight = true;
|
|
250
|
+
try {
|
|
251
|
+
span?.mark('picker_launch');
|
|
252
|
+
const result = await this.directoryPicker.pickDirectory(input);
|
|
253
|
+
span?.mark('picker_result');
|
|
254
|
+
if (span) {
|
|
255
|
+
const outcome = result.kind === 'selected'
|
|
256
|
+
? 'success'
|
|
257
|
+
: result.kind === 'cancelled'
|
|
258
|
+
? 'cancelled'
|
|
259
|
+
: 'failed';
|
|
260
|
+
this.openTelemetry.record(span.finish(outcome, result.kind === 'unavailable' ? result.reason : undefined));
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (span)
|
|
266
|
+
this.openTelemetry.record(span.finish('failed', 'picker_error'));
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
this.pickerInFlight = false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* v0.3.17 — Read-only, bounded preview of a candidate directory.
|
|
275
|
+
*
|
|
276
|
+
* Probes with asynchronous fs so a hung network mount or a broken symlink
|
|
277
|
+
* cannot freeze the Node event loop, and races the whole probe against a
|
|
278
|
+
* short budget. It never registers a workspace, activates a Context, reads
|
|
279
|
+
* file contents, or touches the session catalog beyond a warm-cache peek.
|
|
280
|
+
*/
|
|
281
|
+
async inspectWorkspaceFast(path, source = 'manual', requestId) {
|
|
282
|
+
if (this.closed) {
|
|
283
|
+
throw new errors_1.WebWorkbenchError(503, 'The local Web Host is shutting down.', 'host_closed');
|
|
284
|
+
}
|
|
285
|
+
const span = requestId ? this.openTelemetry.start(requestId, 'inspect') : undefined;
|
|
286
|
+
const inspected = await inspectDirectory(requireCandidatePath(path));
|
|
287
|
+
span?.mark('inspect_fs');
|
|
288
|
+
const existing = this.workspaceRegistry
|
|
289
|
+
.list()
|
|
290
|
+
.find(entry => entry.canonicalPath === inspected.canonicalPath);
|
|
291
|
+
// The preview must never wait on the session catalog: rebuilding it can
|
|
292
|
+
// take a file lock for up to 10s. Use the warm cache when it is already
|
|
293
|
+
// valid, otherwise report the count as deferred rather than blocking the
|
|
294
|
+
// confirmation card.
|
|
295
|
+
const counts = inspected.availability === 'available' ? (0, session_storage_1.peekCachedSessionCounts)() : null;
|
|
296
|
+
span?.mark('inspect_session_count');
|
|
297
|
+
const candidate = Object.freeze({
|
|
298
|
+
canonicalPath: inspected.canonicalPath,
|
|
299
|
+
label: (0, path_1.basename)(inspected.canonicalPath) || inspected.canonicalPath,
|
|
300
|
+
availability: inspected.availability,
|
|
301
|
+
kind: inspected.kind,
|
|
302
|
+
source,
|
|
303
|
+
...(existing ? { existingWorkspaceId: existing.id } : {}),
|
|
304
|
+
...(existing?.pinnedOrder !== undefined ? { pinnedOrder: existing.pinnedOrder } : {}),
|
|
305
|
+
...(inspected.availability === 'available'
|
|
306
|
+
? counts
|
|
307
|
+
? {
|
|
308
|
+
sessionCount: counts.get(inspected.canonicalPath) ?? 0,
|
|
309
|
+
sessionCountStatus: 'ready',
|
|
310
|
+
}
|
|
311
|
+
: { sessionCountStatus: 'deferred' }
|
|
312
|
+
: {}),
|
|
313
|
+
isActive: inspected.canonicalPath === this.workspaceValue,
|
|
314
|
+
});
|
|
315
|
+
if (span) {
|
|
316
|
+
this.openTelemetry.record(span.finish('success', inspected.timedOut ? 'inspect_budget_exceeded' : undefined));
|
|
317
|
+
}
|
|
318
|
+
return candidate;
|
|
319
|
+
}
|
|
223
320
|
listWorkspaceSessions(workspaceId, context) {
|
|
224
321
|
if (context)
|
|
225
322
|
this.assertContextGuard(context);
|
|
@@ -869,31 +966,159 @@ class WebWorkbenchController {
|
|
|
869
966
|
/** v0.3.12 S3 — guarded stage/unstage/commit with repository revision CAS. */
|
|
870
967
|
async gitStage(context, input) {
|
|
871
968
|
this.assertContextGuard(context);
|
|
872
|
-
const paths = this.
|
|
969
|
+
const paths = this.gitService.resolveMutationPaths('stage', input.fileIds);
|
|
873
970
|
await this.assertGitRepositoryRevision(input.expectedRepositoryRevision);
|
|
874
971
|
return this.gitService.stagePaths(paths);
|
|
875
972
|
}
|
|
876
973
|
async gitUnstage(context, input) {
|
|
877
974
|
this.assertContextGuard(context);
|
|
878
|
-
const paths = this.
|
|
975
|
+
const paths = this.gitService.resolveMutationPaths('unstage', input.fileIds);
|
|
879
976
|
await this.assertGitRepositoryRevision(input.expectedRepositoryRevision);
|
|
880
977
|
return this.gitService.unstagePaths(paths);
|
|
881
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* v0.3.17 S4 — history reads.
|
|
981
|
+
*
|
|
982
|
+
* Read-only by construction: they are registered as queries, so none of them can reach the
|
|
983
|
+
* mutation path (`assertMutation` / `executeMutation` are never involved). That is what
|
|
984
|
+
* makes "browsing history never writes to the workspace" a structural property rather than
|
|
985
|
+
* a promise.
|
|
986
|
+
*/
|
|
987
|
+
/**
|
|
988
|
+
* v0.3.17 S6 — translate a Git file token into a Files token (plan G7).
|
|
989
|
+
*
|
|
990
|
+
* The browser never derives one token from the other: the Git token is resolved to a
|
|
991
|
+
* workspace-relative path here, and only the FileReadService mints the Files-side id.
|
|
992
|
+
* That keeps the two id spaces from leaking into each other.
|
|
993
|
+
*/
|
|
994
|
+
async gitFilesTarget(context, gitFileToken) {
|
|
995
|
+
this.assertContextGuard(context);
|
|
996
|
+
const target = this.gitService.resolveTarget(gitFileToken);
|
|
997
|
+
const filesToken = this.fileService.identifyRelativePath(target.path);
|
|
998
|
+
this.assertContextGuard(context);
|
|
999
|
+
return Object.freeze({
|
|
1000
|
+
path: target.path,
|
|
1001
|
+
source: target.source,
|
|
1002
|
+
filesToken,
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
async gitRefs(context) {
|
|
1006
|
+
this.assertContextGuard(context);
|
|
1007
|
+
const result = await this.gitService.refs();
|
|
1008
|
+
this.assertContextGuard(context);
|
|
1009
|
+
return result;
|
|
1010
|
+
}
|
|
1011
|
+
async gitHistory(context, query) {
|
|
1012
|
+
this.assertContextGuard(context);
|
|
1013
|
+
const result = await this.gitService.history(query);
|
|
1014
|
+
this.assertContextGuard(context);
|
|
1015
|
+
return result;
|
|
1016
|
+
}
|
|
1017
|
+
async gitCommitDetail(context, oid, parentIndex = 0) {
|
|
1018
|
+
this.assertContextGuard(context);
|
|
1019
|
+
const result = await this.gitService.commitDetail(oid, parentIndex);
|
|
1020
|
+
this.assertContextGuard(context);
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
async gitCommitFiles(context, oid, parentIndex = 0) {
|
|
1024
|
+
this.assertContextGuard(context);
|
|
1025
|
+
const result = await this.gitService.commitFiles(oid, parentIndex);
|
|
1026
|
+
this.assertContextGuard(context);
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
1029
|
+
async gitCommitDiff(context, input) {
|
|
1030
|
+
this.assertContextGuard(context);
|
|
1031
|
+
const result = await this.gitService.commitDiffDocument(input);
|
|
1032
|
+
this.assertContextGuard(context);
|
|
1033
|
+
return result;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* v0.3.17 S5 — branch/version comparison. Registered as a query: the compare view can
|
|
1037
|
+
* never reach the mutation path.
|
|
1038
|
+
*/
|
|
1039
|
+
async gitCompare(context, input) {
|
|
1040
|
+
this.assertContextGuard(context);
|
|
1041
|
+
const result = await this.gitService.compare(input);
|
|
1042
|
+
this.assertContextGuard(context);
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1045
|
+
async gitCompareFileDiff(context, input) {
|
|
1046
|
+
this.assertContextGuard(context);
|
|
1047
|
+
const result = await this.gitService.compareFileDiffDocument(input);
|
|
1048
|
+
this.assertContextGuard(context);
|
|
1049
|
+
return result;
|
|
1050
|
+
}
|
|
1051
|
+
/** v0.3.17 S5 — the commit graph, as git itself drew it. Read-only. */
|
|
1052
|
+
async gitGraph(context, input) {
|
|
1053
|
+
this.assertContextGuard(context);
|
|
1054
|
+
const result = await this.gitService.graph(input);
|
|
1055
|
+
this.assertContextGuard(context);
|
|
1056
|
+
return result;
|
|
1057
|
+
}
|
|
1058
|
+
async gitBlame(context, input) {
|
|
1059
|
+
this.assertContextGuard(context);
|
|
1060
|
+
const result = await this.gitService.blame(input);
|
|
1061
|
+
this.assertContextGuard(context);
|
|
1062
|
+
return result;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* v0.3.17 S5 — conflict stages, historical blobs and gitlink entries. All query-only.
|
|
1066
|
+
*/
|
|
1067
|
+
async gitConflictVersions(context, path) {
|
|
1068
|
+
this.assertContextGuard(context);
|
|
1069
|
+
const result = await this.gitService.conflictVersions(path);
|
|
1070
|
+
this.assertContextGuard(context);
|
|
1071
|
+
return result;
|
|
1072
|
+
}
|
|
1073
|
+
async gitBlob(context, input) {
|
|
1074
|
+
this.assertContextGuard(context);
|
|
1075
|
+
const result = await this.gitService.fileBlob(input);
|
|
1076
|
+
this.assertContextGuard(context);
|
|
1077
|
+
return result;
|
|
1078
|
+
}
|
|
1079
|
+
async gitSubmodules(context) {
|
|
1080
|
+
this.assertContextGuard(context);
|
|
1081
|
+
const result = await this.gitService.submodules();
|
|
1082
|
+
this.assertContextGuard(context);
|
|
1083
|
+
return result;
|
|
1084
|
+
}
|
|
1085
|
+
async gitFileHistory(context, input) {
|
|
1086
|
+
this.assertContextGuard(context);
|
|
1087
|
+
const result = await this.gitService.fileHistory(input);
|
|
1088
|
+
this.assertContextGuard(context);
|
|
1089
|
+
return result;
|
|
1090
|
+
}
|
|
1091
|
+
/** v0.3.17 S3 — preview of what the index would commit, independent of list filters. */
|
|
1092
|
+
async gitCommitPreview(context) {
|
|
1093
|
+
this.assertContextGuard(context);
|
|
1094
|
+
const result = await this.gitService.commitPreview();
|
|
1095
|
+
this.assertContextGuard(context);
|
|
1096
|
+
return result;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* v0.3.17 S3 — commit against the frozen revision the form showed. Identity, signing and
|
|
1100
|
+
* hooks come from the repository's own configuration (plan §7.9).
|
|
1101
|
+
*/
|
|
882
1102
|
async gitCommit(context, input) {
|
|
1103
|
+
this.assertContextGuard(context);
|
|
1104
|
+
const result = await this.gitService.commit(input);
|
|
1105
|
+
this.assertContextGuard(context);
|
|
1106
|
+
return result;
|
|
1107
|
+
}
|
|
1108
|
+
/** v0.3.17 S3 — Host-rebuilt hunk or line selection applied to the index only. */
|
|
1109
|
+
async gitApplyPatch(context, input) {
|
|
883
1110
|
this.assertContextGuard(context);
|
|
884
1111
|
await this.assertGitRepositoryRevision(input.expectedRepositoryRevision);
|
|
885
|
-
|
|
1112
|
+
const result = await this.gitService.applySelection(input);
|
|
1113
|
+
this.assertContextGuard(context);
|
|
1114
|
+
return result;
|
|
886
1115
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
throw new errors_1.WebWorkbenchError(400, 'A file id is invalid.', 'file_id_invalid');
|
|
894
|
-
}
|
|
895
|
-
return this.fileService.pathForFileId(fileId);
|
|
896
|
-
});
|
|
1116
|
+
/** v0.3.17 S2 — structured diff document for the selected comparison source. */
|
|
1117
|
+
async gitDiffDocument(context, input) {
|
|
1118
|
+
this.assertContextGuard(context);
|
|
1119
|
+
const result = await this.gitService.diffDocument(input);
|
|
1120
|
+
this.assertContextGuard(context);
|
|
1121
|
+
return result;
|
|
897
1122
|
}
|
|
898
1123
|
async assertGitRepositoryRevision(expected) {
|
|
899
1124
|
if (typeof expected !== 'string' || !/^[0-9a-f]{40,64}$/u.test(expected)) {
|
|
@@ -1051,7 +1276,7 @@ class WebWorkbenchController {
|
|
|
1051
1276
|
throw new errors_1.WebWorkbenchError(409, 'Session has an active runtime; stop it before deleting.', 'session_busy');
|
|
1052
1277
|
}
|
|
1053
1278
|
}
|
|
1054
|
-
async switchWorkspace(path, context) {
|
|
1279
|
+
async switchWorkspace(path, context, requestId) {
|
|
1055
1280
|
this.assertContextGuard(context);
|
|
1056
1281
|
const next = canonicalDirectory(path);
|
|
1057
1282
|
if (next === this.workspaceValue)
|
|
@@ -1067,6 +1292,7 @@ class WebWorkbenchController {
|
|
|
1067
1292
|
expectedContextRevision: context.expectedContextRevision,
|
|
1068
1293
|
workspaceId: entry.id,
|
|
1069
1294
|
sessionId: null,
|
|
1295
|
+
...(requestId ? { requestId } : {}),
|
|
1070
1296
|
});
|
|
1071
1297
|
}
|
|
1072
1298
|
async activateContext(input) {
|
|
@@ -1089,6 +1315,12 @@ class WebWorkbenchController {
|
|
|
1089
1315
|
// selection never mutates the Host Context or any Session actor.
|
|
1090
1316
|
return;
|
|
1091
1317
|
}
|
|
1318
|
+
// v0.3.17 — trace the transition so "confirm → usable" can be attributed
|
|
1319
|
+
// to a stage instead of guessed at.
|
|
1320
|
+
const span = input.requestId
|
|
1321
|
+
? this.openTelemetry.start(input.requestId, 'activate')
|
|
1322
|
+
: undefined;
|
|
1323
|
+
span?.mark('activate_admitted');
|
|
1092
1324
|
this.assertReadyForTransition('activate a Context');
|
|
1093
1325
|
const previousWorkspace = this.workspaceValue;
|
|
1094
1326
|
const previousSessionId = currentSessionId;
|
|
@@ -1112,7 +1344,9 @@ class WebWorkbenchController {
|
|
|
1112
1344
|
// actors of that Workspace still reference. Install the target
|
|
1113
1345
|
// control plane first, then release the previous one's mutable state.
|
|
1114
1346
|
await this.installRuntime(targetWorkspace, false);
|
|
1347
|
+
span?.mark('runtime_install');
|
|
1115
1348
|
await previousRuntime.shutdown();
|
|
1349
|
+
span?.mark('previous_runtime_shutdown');
|
|
1116
1350
|
}
|
|
1117
1351
|
this.workspaceRegistry.register(targetWorkspace, { activated: true });
|
|
1118
1352
|
}
|
|
@@ -1138,7 +1372,18 @@ class WebWorkbenchController {
|
|
|
1138
1372
|
this.emitWorkspaceResourceInvalidation('context-change');
|
|
1139
1373
|
if (targetWorkspace !== previousWorkspace)
|
|
1140
1374
|
this.emitSettingsWorkspaceChange();
|
|
1141
|
-
})()
|
|
1375
|
+
})()
|
|
1376
|
+
.then(() => {
|
|
1377
|
+
if (span)
|
|
1378
|
+
this.openTelemetry.record(span.finish('success'));
|
|
1379
|
+
})
|
|
1380
|
+
.catch(error => {
|
|
1381
|
+
if (span) {
|
|
1382
|
+
this.openTelemetry.record(span.finish('failed', error instanceof errors_1.WebWorkbenchError ? error.code : 'activate_error'));
|
|
1383
|
+
}
|
|
1384
|
+
throw error;
|
|
1385
|
+
})
|
|
1386
|
+
.finally(() => {
|
|
1142
1387
|
if (this.transition === transition)
|
|
1143
1388
|
this.transition = undefined;
|
|
1144
1389
|
});
|
|
@@ -1519,6 +1764,8 @@ class WebWorkbenchController {
|
|
|
1519
1764
|
harness,
|
|
1520
1765
|
eventStream: this.eventHub.snapshot(),
|
|
1521
1766
|
workspaceKernel: this.runtimeValue.workspaceRuntimeKernel?.diagnostics() ?? null,
|
|
1767
|
+
// v0.3.17 — bounded, path-free open-path traces (picker / inspect / activate).
|
|
1768
|
+
workspaceOpen: this.openTelemetry.snapshot(),
|
|
1522
1769
|
session: {
|
|
1523
1770
|
...this.sessionActivity,
|
|
1524
1771
|
actors: this.sessionRegistry.stats(),
|
|
@@ -2456,6 +2703,127 @@ async function captureShutdownFailure(failures, operation) {
|
|
|
2456
2703
|
failures.push(error);
|
|
2457
2704
|
}
|
|
2458
2705
|
}
|
|
2706
|
+
/** v0.3.16 — markers that make a directory interesting as a project. */
|
|
2707
|
+
const PROJECT_SIGNAL_MARKERS = [
|
|
2708
|
+
'.git',
|
|
2709
|
+
'package.json',
|
|
2710
|
+
'pyproject.toml',
|
|
2711
|
+
'Cargo.toml',
|
|
2712
|
+
'go.mod',
|
|
2713
|
+
];
|
|
2714
|
+
/**
|
|
2715
|
+
* True when a directory looks like a project root. Only marker *existence* is
|
|
2716
|
+
* checked — never file contents — which is what roots discovery filters on.
|
|
2717
|
+
*/
|
|
2718
|
+
function hasProjectSignal(canonicalPath) {
|
|
2719
|
+
return PROJECT_SIGNAL_MARKERS.some(marker => {
|
|
2720
|
+
try {
|
|
2721
|
+
return (0, fs_1.existsSync)((0, path_1.join)(canonicalPath, marker));
|
|
2722
|
+
}
|
|
2723
|
+
catch {
|
|
2724
|
+
return false;
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
function requireCandidatePath(path) {
|
|
2729
|
+
const trimmed = path.trim();
|
|
2730
|
+
if (!trimmed) {
|
|
2731
|
+
throw new errors_1.WebWorkbenchError(400, 'A directory path is required.', 'invalid_request');
|
|
2732
|
+
}
|
|
2733
|
+
if (trimmed.length > 4096) {
|
|
2734
|
+
throw new errors_1.WebWorkbenchError(400, 'The directory path is too long.', 'invalid_request');
|
|
2735
|
+
}
|
|
2736
|
+
return trimmed;
|
|
2737
|
+
}
|
|
2738
|
+
/** Short budget: an unresponsive mount must never make the picker feel stuck. */
|
|
2739
|
+
const INSPECT_BUDGET_MS = 250;
|
|
2740
|
+
async function hasPath(absolute) {
|
|
2741
|
+
try {
|
|
2742
|
+
await (0, promises_1.access)(absolute);
|
|
2743
|
+
return true;
|
|
2744
|
+
}
|
|
2745
|
+
catch {
|
|
2746
|
+
return false;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
/** `.git` as a directory (clone) or a file (worktree/submodule) both count. */
|
|
2750
|
+
async function detectWorkspaceKind(canonicalPath) {
|
|
2751
|
+
try {
|
|
2752
|
+
await (0, promises_1.access)((0, path_1.join)(canonicalPath, '.git'));
|
|
2753
|
+
return 'git';
|
|
2754
|
+
}
|
|
2755
|
+
catch {
|
|
2756
|
+
return 'folder';
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
async function probeDirectory(absolute) {
|
|
2760
|
+
let canonical;
|
|
2761
|
+
try {
|
|
2762
|
+
canonical = await (0, promises_1.realpath)(absolute);
|
|
2763
|
+
}
|
|
2764
|
+
catch {
|
|
2765
|
+
return {
|
|
2766
|
+
canonicalPath: absolute,
|
|
2767
|
+
availability: (await hasPath(absolute)) ? 'unreadable' : 'missing',
|
|
2768
|
+
kind: 'folder',
|
|
2769
|
+
timedOut: false,
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
let stats;
|
|
2773
|
+
try {
|
|
2774
|
+
stats = await (0, promises_1.stat)(canonical);
|
|
2775
|
+
}
|
|
2776
|
+
catch {
|
|
2777
|
+
return {
|
|
2778
|
+
canonicalPath: canonical,
|
|
2779
|
+
availability: 'unreadable',
|
|
2780
|
+
kind: 'folder',
|
|
2781
|
+
timedOut: false,
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
if (!stats.isDirectory()) {
|
|
2785
|
+
return {
|
|
2786
|
+
canonicalPath: canonical,
|
|
2787
|
+
availability: 'not_directory',
|
|
2788
|
+
kind: 'folder',
|
|
2789
|
+
timedOut: false,
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
return {
|
|
2793
|
+
canonicalPath: canonical,
|
|
2794
|
+
availability: 'available',
|
|
2795
|
+
kind: await detectWorkspaceKind(canonical),
|
|
2796
|
+
timedOut: false,
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
/**
|
|
2800
|
+
* Non-throwing canonicalization for the read-only inspector. `canonicalDirectory`
|
|
2801
|
+
* below is the mutation path — it rejects missing or unreadable directories,
|
|
2802
|
+
* which is what activation wants but not what a preview needs.
|
|
2803
|
+
*
|
|
2804
|
+
* The whole probe is raced against a short budget so an unresponsive network
|
|
2805
|
+
* mount cannot freeze the event loop; on timeout the directory is reported
|
|
2806
|
+
* `unreadable` and the caller can retry.
|
|
2807
|
+
*/
|
|
2808
|
+
async function inspectDirectory(path) {
|
|
2809
|
+
const absolute = (0, path_1.resolve)(path);
|
|
2810
|
+
let timer;
|
|
2811
|
+
const budget = new Promise(resolveBudget => {
|
|
2812
|
+
timer = setTimeout(() => resolveBudget({
|
|
2813
|
+
canonicalPath: absolute,
|
|
2814
|
+
availability: 'unreadable',
|
|
2815
|
+
kind: 'folder',
|
|
2816
|
+
timedOut: true,
|
|
2817
|
+
}), INSPECT_BUDGET_MS);
|
|
2818
|
+
});
|
|
2819
|
+
try {
|
|
2820
|
+
return await Promise.race([probeDirectory(absolute), budget]);
|
|
2821
|
+
}
|
|
2822
|
+
finally {
|
|
2823
|
+
if (timer)
|
|
2824
|
+
clearTimeout(timer);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2459
2827
|
function canonicalDirectory(path) {
|
|
2460
2828
|
const candidate = (0, path_1.resolve)(path);
|
|
2461
2829
|
let canonical;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.3.17 — LOCAL WORKSPACE open-path telemetry.
|
|
3
|
+
*
|
|
4
|
+
* Answers "which segment is slow" without ever recording a path, a label or
|
|
5
|
+
* file content. Every trace carries its own request id, an operation, an
|
|
6
|
+
* outcome and per-stage milliseconds. The ring buffer is bounded and is exposed
|
|
7
|
+
* read-only through Diagnostics, so E2E and the diagnostics panel can compare a
|
|
8
|
+
* cold and a warm open instead of guessing.
|
|
9
|
+
*/
|
|
10
|
+
export type WorkspaceOpenStage = 'picker_launch' | 'picker_result' | 'inspect_fs' | 'inspect_session_count' | 'activate_admitted' | 'runtime_install' | 'previous_runtime_shutdown' | 'baseline_bootstrap' | 'baseline_collections' | 'baseline_snapshot' | 'sse_resumed' | 'interactive';
|
|
11
|
+
export type WorkspaceOpenOperation = 'pick-directory' | 'inspect' | 'activate';
|
|
12
|
+
export type WorkspaceOpenOutcome = 'success' | 'cancelled' | 'failed';
|
|
13
|
+
export interface WorkspaceOpenTraceV1 {
|
|
14
|
+
readonly requestId: string;
|
|
15
|
+
readonly operation: WorkspaceOpenOperation;
|
|
16
|
+
readonly outcome: WorkspaceOpenOutcome;
|
|
17
|
+
/** Milliseconds from span start to the moment each stage completed. */
|
|
18
|
+
readonly stages: Readonly<Partial<Record<WorkspaceOpenStage, number>>>;
|
|
19
|
+
readonly totalMs: number;
|
|
20
|
+
readonly errorCode?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface WorkspaceOpenTelemetrySnapshotV1 {
|
|
23
|
+
readonly retained: number;
|
|
24
|
+
readonly limit: number;
|
|
25
|
+
readonly recent: readonly WorkspaceOpenTraceV1[];
|
|
26
|
+
}
|
|
27
|
+
export interface WorkspaceOpenTelemetryOptions {
|
|
28
|
+
readonly limit?: number;
|
|
29
|
+
readonly now?: () => number;
|
|
30
|
+
}
|
|
31
|
+
/** One in-flight operation. Stages are recorded once; later marks are ignored. */
|
|
32
|
+
export declare class WorkspaceOpenSpanV1 {
|
|
33
|
+
readonly requestId: string;
|
|
34
|
+
readonly operation: WorkspaceOpenOperation;
|
|
35
|
+
private readonly now;
|
|
36
|
+
private readonly stages;
|
|
37
|
+
private readonly startedAt;
|
|
38
|
+
private finished;
|
|
39
|
+
constructor(requestId: string, operation: WorkspaceOpenOperation, now: () => number);
|
|
40
|
+
mark(stage: WorkspaceOpenStage): void;
|
|
41
|
+
has(stage: WorkspaceOpenStage): boolean;
|
|
42
|
+
finish(outcome: WorkspaceOpenOutcome, errorCode?: string): WorkspaceOpenTraceV1;
|
|
43
|
+
/** Flush without recording (used when tracing is not interesting). */
|
|
44
|
+
discard(): void;
|
|
45
|
+
}
|
|
46
|
+
export declare class WorkspaceOpenTelemetryV1 {
|
|
47
|
+
private readonly limit;
|
|
48
|
+
private readonly now;
|
|
49
|
+
private readonly entries;
|
|
50
|
+
constructor(options?: WorkspaceOpenTelemetryOptions);
|
|
51
|
+
start(requestId: string, operation: WorkspaceOpenOperation): WorkspaceOpenSpanV1;
|
|
52
|
+
record(trace: WorkspaceOpenTraceV1): void;
|
|
53
|
+
snapshot(): WorkspaceOpenTelemetrySnapshotV1;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=workspace-open-telemetry.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* v0.3.17 — LOCAL WORKSPACE open-path telemetry.
|
|
4
|
+
*
|
|
5
|
+
* Answers "which segment is slow" without ever recording a path, a label or
|
|
6
|
+
* file content. Every trace carries its own request id, an operation, an
|
|
7
|
+
* outcome and per-stage milliseconds. The ring buffer is bounded and is exposed
|
|
8
|
+
* read-only through Diagnostics, so E2E and the diagnostics panel can compare a
|
|
9
|
+
* cold and a warm open instead of guessing.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WorkspaceOpenTelemetryV1 = exports.WorkspaceOpenSpanV1 = void 0;
|
|
13
|
+
const DEFAULT_LIMIT = 50;
|
|
14
|
+
/** One in-flight operation. Stages are recorded once; later marks are ignored. */
|
|
15
|
+
class WorkspaceOpenSpanV1 {
|
|
16
|
+
constructor(requestId, operation, now) {
|
|
17
|
+
this.requestId = requestId;
|
|
18
|
+
this.operation = operation;
|
|
19
|
+
this.now = now;
|
|
20
|
+
this.stages = {};
|
|
21
|
+
this.finished = false;
|
|
22
|
+
this.startedAt = now();
|
|
23
|
+
}
|
|
24
|
+
mark(stage) {
|
|
25
|
+
if (this.finished || this.stages[stage] !== undefined)
|
|
26
|
+
return;
|
|
27
|
+
this.stages[stage] = Math.max(0, Math.round(this.now() - this.startedAt));
|
|
28
|
+
}
|
|
29
|
+
has(stage) {
|
|
30
|
+
return this.stages[stage] !== undefined;
|
|
31
|
+
}
|
|
32
|
+
finish(outcome, errorCode) {
|
|
33
|
+
this.finished = true;
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
requestId: this.requestId,
|
|
36
|
+
operation: this.operation,
|
|
37
|
+
outcome,
|
|
38
|
+
stages: Object.freeze({ ...this.stages }),
|
|
39
|
+
totalMs: Math.max(0, Math.round(this.now() - this.startedAt)),
|
|
40
|
+
...(errorCode ? { errorCode } : {}),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Flush without recording (used when tracing is not interesting). */
|
|
44
|
+
discard() {
|
|
45
|
+
this.finished = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.WorkspaceOpenSpanV1 = WorkspaceOpenSpanV1;
|
|
49
|
+
class WorkspaceOpenTelemetryV1 {
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
this.entries = [];
|
|
52
|
+
this.limit = Math.max(1, options.limit ?? DEFAULT_LIMIT);
|
|
53
|
+
this.now = options.now ?? (() => performance.now());
|
|
54
|
+
}
|
|
55
|
+
start(requestId, operation) {
|
|
56
|
+
return new WorkspaceOpenSpanV1(requestId, operation, this.now);
|
|
57
|
+
}
|
|
58
|
+
record(trace) {
|
|
59
|
+
this.entries.push(trace);
|
|
60
|
+
if (this.entries.length > this.limit) {
|
|
61
|
+
this.entries.splice(0, this.entries.length - this.limit);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
snapshot() {
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
retained: this.entries.length,
|
|
67
|
+
limit: this.limit,
|
|
68
|
+
recent: Object.freeze([...this.entries]),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.WorkspaceOpenTelemetryV1 = WorkspaceOpenTelemetryV1;
|
|
73
|
+
//# sourceMappingURL=workspace-open-telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as a,j as e,I as D,s as ye,C as be,W as ge}from"./index-Drja0_BT.js";import{u as Ce,R as Ne}from"./useAutoNotice-DfQi4G1X.js";function Le({workspaceId:s,refreshEpoch:o,actions:l,navigatorWidthPx:r,onNavigatorWidthCommit:d,revealRequest:c}){const[f,m]=a.useState({}),[h,j]=a.useState(new Set(["workspace-root"])),[i,N]=a.useState(null),[F,B]=a.useState(""),[M,G]=a.useState(null),[Y,P]=a.useState(!1),[H,K]=a.useState(""),{notice:J,showNotice:$,clearNotice:V}=Ce(),[T,ue]=a.useState(""),[Z,_]=a.useState({}),[I,A]=a.useState(""),[O,R]=a.useState("view"),[L,w]=a.useState(""),[k,q]=a.useState(!1),[de,S]=a.useState(!1),[fe,me]=a.useState(""),[xe,W]=a.useState(!1),x=a.useRef(0),C=a.useRef(0),ee=a.useRef(null),E=async(t,n=!1,b=x.current)=>{if(b!==x.current)return;const p=f[t],g=n?p?.nextCursor:void 0;if(!(n&&!g)){m(u=>({...u,[t]:{items:p?.items??[],nextCursor:p?.nextCursor??null,revision:p?.revision??"",loading:!0}}));try{const u=await l.listFiles(t,g??void 0);if(b!==x.current)return;m(v=>({...v,[t]:{items:n?ke(v[t]?.items??[],u.items):u.items,nextCursor:u.nextCursor,revision:u.revision,loading:!1}}))}catch(u){if(b!==x.current)return;if(n&&U(u)){$("目录已变化,已重新载入第一页。"),E(t,!1,b);return}m(v=>({...v,[t]:{items:v[t]?.items??[],nextCursor:v[t]?.nextCursor??null,revision:v[t]?.revision??"",loading:!1,error:z(u)}}))}}},Q=async(t=x.current)=>{try{const n=await l.gitStatus();if(t!==x.current)return;_(Se(n)),A(n.nextCursor?"Git 装饰仅显示首批 200 个变更;在 Git 面板继续分页查看。":"")}catch(n){if(t!==x.current)return;_({}),A(`Git 状态装饰不可用:${z(n)}`)}},te=a.useRef(s);a.useEffect(()=>{const t=x.current+1;x.current=t,C.current+=1;const n=te.current===s?ee.current:null;te.current=s,m({}),j(new Set(["workspace-root"])),N(n),B(""),G(null),P(!1),K(""),V(),_({}),A(""),s&&(E("workspace-root",!1,t),Q(t),n&&y(n,!1,!1,t))},[o,s]);const se=a.useRef(null),y=async(t,n=!1,b=!1,p=x.current,g=C.current+1)=>{if(p===x.current&&!(!t.readable||t.sensitive||X(t))){C.current=g,N(t),K(""),n||(B(""),G(null),P(!1),R("view"),w(""),S(!1)),b||V();try{const u=await l.readFileContent(t.id,n?M??void 0:void 0);if(p!==x.current||g!==C.current)return;P(u.binary),B(v=>n?`${v}${u.content??""}`:u.content??""),G(u.nextCursor),me(u.revision),b&&$("文件已变化,已从第一页重新载入。")}catch(u){if(p!==x.current||g!==C.current)return;if(n&&U(u)){await y(t,!1,!0,p,g);return}K(z(u))}}},re=De({selected:i,binary:Y,hasMorePages:M!==null}),he=()=>{w(F),S(!1),R("edit")},ie=()=>{if(L!==F){W(!0);return}ne()},ne=()=>{R("view"),w(""),S(!1),W(!1)},je=async()=>{if(!(!i||k)){q(!0),S(!1);try{await l.writeFileContent(i.id,L,fe),R("view"),w(""),await y(i,!1,!0,x.current,C.current+1),$("已保存文件。","success"),Q()}catch(t){U(t)?S(!0):$(z(t),"error")}finally{q(!1)}}},ae=t=>{const n=new Set(h);n.has(t)?n.delete(t):(n.add(t),f[t]||E(t)),j(n)};a.useEffect(()=>{c&&se.current!==c.id&&(se.current=c.id,y({id:c.fileId,name:c.displayPath.split("/").pop()??c.displayPath,displayPath:c.displayPath,kind:"file",modifiedAt:new Date().toISOString(),sensitive:!1,readable:!0}))},[c?.id]),a.useEffect(()=>{ee.current=i},[i]);const pe=a.useMemo(()=>Object.values(f).flatMap(t=>t.items),[f]),oe=T.trim()?pe.filter(t=>t.name.toLocaleLowerCase().includes(T.toLocaleLowerCase())):null,ve=!!f["workspace-root"]?.loading;return e.jsxs("div",{className:"work-resource-panel files-panel",children:[e.jsxs("div",{className:"resource-toolbar",children:[e.jsxs("label",{className:"resource-search",children:[e.jsx("span",{className:"sr-only",children:"搜索已加载文件"}),e.jsx(D,{name:"search",size:16}),e.jsx("input",{type:"search",value:T,onChange:t=>ue(t.target.value),placeholder:"搜索已加载文件",title:"搜索仅覆盖已加载的目录和文件"})]}),e.jsx("button",{type:"button",className:"icon-button","aria-label":"刷新文件树","aria-busy":ve,onClick:()=>{E("workspace-root"),Q()},children:e.jsx(D,{name:"refresh",size:16})})]}),e.jsxs(Ne,{panelId:"files",navigatorWidthPx:r,onNavigatorWidthCommit:d,contentLabel:"文件预览",navigatorLabel:"工作区文件",handleLabel:"调整文件目录宽度",contentClassName:"file-preview",navigatorClassName:"file-tree",children:[e.jsxs(e.Fragment,{children:[I?e.jsx("p",{className:"resource-hint",role:"status",children:I}):null,oe?e.jsx(e.Fragment,{children:e.jsx("ul",{role:"list",className:"file-node-list search-results",children:oe.map(t=>e.jsx(ce,{node:t,depth:0,expanded:!1,selected:i?.id===t.id,gitLabels:Z[t.displayPath]??[],onToggle:ae,onSelect:n=>{y(n)}},t.id))})}):e.jsx(le,{parentId:"workspace-root",depth:0,directories:f,expanded:h,selectedId:i?.id,gitDecorations:Z,onToggle:ae,onSelect:t=>{y(t)},onLoadMore:t=>{E(t,!0)}})]}),e.jsxs(e.Fragment,{children:[J?e.jsx("p",{className:"resource-notice",role:"status",children:J.text}):null,i?e.jsxs(e.Fragment,{children:[e.jsxs("header",{children:[e.jsx("strong",{children:i.name}),e.jsx("span",{children:Fe(i.sizeBytes??0)})]}),H?e.jsx("p",{className:"resource-error",role:"alert",children:H}):Y?e.jsxs("div",{className:"resource-empty",children:[e.jsx(D,{name:"code",size:16}),e.jsx("strong",{title:"出于安全和性能考虑,只显示元数据。",children:"二进制文件"})]}):e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"file-preview-actions",children:O==="view"?e.jsx("button",{type:"button",className:"text-button",disabled:!re,title:re?void 0:"仅支持编辑 512 KB 内的 UTF-8 文本文件",onClick:he,children:"编辑"}):e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",className:"primary-button",disabled:k||L===F,"aria-busy":k,onClick:()=>{je()},children:k?"保存中…":"保存"}),e.jsx("button",{type:"button",className:"text-button",disabled:k,onClick:ie,children:"取消"})]})}),de?e.jsxs("p",{className:"resource-error",role:"alert",children:["文件已在别处变更,保存被拒绝。",e.jsx("button",{type:"button",className:"text-button",onClick:()=>{y(i,!1,!0)},children:"重新加载"})]}):null,O==="edit"?e.jsx("textarea",{className:"file-editor",value:L,spellCheck:!1,"aria-label":`编辑文件 ${i.name}`,onChange:t=>w(t.target.value),onKeyDown:t=>{t.key==="Escape"&&(t.preventDefault(),ie())}}):e.jsx("pre",{tabIndex:0,className:"file-code-view","aria-label":`文件内容 ${i.name}`,children:ye(F)})]}),M&&O==="view"?e.jsx("button",{type:"button",className:"secondary-button",onClick:()=>{y(i,!0)},children:"加载更多内容"}):null]}):e.jsxs("div",{className:"resource-empty",children:[e.jsx(D,{name:"workspace",size:16}),e.jsx("strong",{children:"选择文件预览"}),e.jsx("p",{title:"敏感文件、工作区外链接和二进制正文不会返回浏览器。",children:"受限内容不会返回浏览器。"})]})]})]}),e.jsx(be,{open:xe,onClose:()=>W(!1),title:"放弃未保存的修改?",body:"当前草稿尚未保存,放弃后将回到文件的最新内容。",confirmLabel:"放弃修改",danger:!0,onConfirm:ne})]})}function le({parentId:s,depth:o,directories:l,expanded:r,selectedId:d,gitDecorations:c,onToggle:f,onSelect:m,onLoadMore:h}){const j=l[s];return j?j.error?e.jsx("p",{className:"resource-error",role:"alert",children:j.error}):e.jsxs("ul",{role:"list",className:"file-node-list",children:[j.items.map(i=>{const N=r.has(i.id);return e.jsxs("li",{children:[e.jsx(ce,{node:i,depth:o,expanded:N,selected:d===i.id,gitLabels:c[i.displayPath]??[],onToggle:f,onSelect:m}),X(i)&&N?e.jsx(le,{parentId:i.id,depth:o+1,directories:l,expanded:r,selectedId:d,gitDecorations:c,onToggle:f,onSelect:m,onLoadMore:h}):null]},i.id)}),j.loading?e.jsx("li",{className:"resource-loading",children:"正在读取…"}):null,j.nextCursor?e.jsx("li",{children:e.jsx("button",{type:"button",className:"text-button",onClick:()=>h(s),children:"加载更多"})}):null]}):e.jsx("p",{className:"resource-loading",children:"正在读取…"})}function ce({node:s,depth:o,expanded:l,selected:r,gitLabels:d,onToggle:c,onSelect:f}){const m=s.sensitive||!s.readable,h=X(s);return e.jsxs("button",{type:"button",className:`file-node ${r?"selected":""} ${m?"blocked":""}`,style:{paddingInlineStart:`${8+o*15}px`},"aria-expanded":h?l:void 0,"aria-label":`${we(s)} ${s.name}${m?",不可读取":""}${d.length?`,Git ${d.join("、")}`:""}`,onClick:()=>h?c(s.id):f(s),disabled:m,children:[e.jsx(D,{name:h?"workspace":"code",size:16}),e.jsx("span",{children:s.name}),s.kind==="symlink"?e.jsx("small",{children:"链接"}):null,s.sensitive?e.jsx("small",{children:"敏感"}):null,d.length?e.jsx("small",{className:"file-git-status",title:`Git ${d.join("、")}`,children:d.join(" · ")}):null]})}function we(s){return s.kind==="directory"?"目录":s.kind==="symlink"?s.targetKind==="directory"?"符号链接目录":"符号链接":"文件"}function X(s){return s.kind==="directory"||s.kind==="symlink"&&s.targetKind==="directory"}function ke(s,o){const l=new Map(s.map(r=>[r.id,r]));for(const r of o)l.set(r.id,r);return[...l.values()]}function Se(s){if(!s.isRepository)return{};const o=new Map,l=(r,d)=>{const c=r.replace(/\\/gu,"/").replace(/^\.\//u,"");if(!c)return;const f=o.get(c)??new Set;f.add(d),o.set(c,f);const m=c.split("/");for(let h=1;h<m.length;h+=1){const j=m.slice(0,h).join("/"),i=o.get(j)??new Set;i.add("含变更"),o.set(j,i)}};return s.conflicted.forEach(r=>l(r.path,"冲突")),s.staged.forEach(r=>l(r.path,"已暂存")),s.unstaged.forEach(r=>l(r.path,"未暂存")),s.untracked.forEach(r=>l(r.path,"未跟踪")),Object.fromEntries([...o].map(([r,d])=>[r,Object.freeze([...d])]))}const Ee=512*1024;function De(s){const{selected:o,binary:l,hasMorePages:r}=s;return!o||l||o.sensitive||!o.readable||r?!1:(o.sizeBytes??0)<=Ee}function Fe(s){return s<1024?`${s} B`:s<1048576?`${(s/1024).toFixed(1)} KB`:`${(s/1048576).toFixed(1)} MB`}function U(s){return s instanceof ge&&s.code==="file_revision_conflict"}function z(s){return s instanceof Error?s.message:"文件请求失败。"}export{Ee as FILE_EDIT_MAX_BYTES,Le as FilesPanel,De as canEditFileContent};
|