@orion-agents/orion-code 0.3.21 → 0.3.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +67 -6
  2. package/README.md +6 -6
  3. package/README.zh-CN.md +5 -5
  4. package/dist/harness/failure-attribution.d.ts +23 -0
  5. package/dist/harness/failure-attribution.js +103 -0
  6. package/dist/harness/verified-repair.d.ts +71 -0
  7. package/dist/harness/verified-repair.js +212 -0
  8. package/dist/product/paths.d.ts +20 -1
  9. package/dist/product/paths.js +293 -10
  10. package/dist/runtime/release-receipts.d.ts +3 -3
  11. package/dist/runtime/release-receipts.js +29 -0
  12. package/dist/runtime/tool-gateway.d.ts +6 -0
  13. package/dist/runtime/tool-gateway.js +37 -7
  14. package/dist/services/config-dir.d.ts +3 -2
  15. package/dist/services/config-dir.js +6 -29
  16. package/dist/services/repo-intelligence/index.d.ts +11 -0
  17. package/dist/services/repo-intelligence/index.js +20 -0
  18. package/dist/services/repo-intelligence/recall.d.ts +44 -0
  19. package/dist/services/repo-intelligence/recall.js +115 -0
  20. package/dist/services/repo-intelligence/symbol-index.d.ts +52 -0
  21. package/dist/services/repo-intelligence/symbol-index.js +258 -0
  22. package/dist/services/session-storage.d.ts +18 -2
  23. package/dist/services/session-storage.js +196 -60
  24. package/dist/web/protocol.d.ts +8 -0
  25. package/dist/web/workbench-controller.js +46 -2
  26. package/dist/web-client/assets/{FilesPanel-CKeXPofZ.js → FilesPanel-BjEDjE7l.js} +1 -1
  27. package/dist/web-client/assets/{GitPanel-DollPxv0.js → GitPanel-DvgAZDRA.js} +1 -1
  28. package/dist/web-client/assets/{ReviewPanel-CNADacIn.js → ReviewPanel-BMPaYKDY.js} +1 -1
  29. package/dist/web-client/assets/{TerminalPanel-Cyns4uJt.js → TerminalPanel-Da1ZEhGF.js} +1 -1
  30. package/dist/web-client/assets/index-DZOlUgt6.js +20 -0
  31. package/dist/web-client/assets/{useAutoNotice-BLADeFFj.js → useAutoNotice-Ylj5bfSH.js} +1 -1
  32. package/dist/web-client/index.html +1 -1
  33. package/npm-shrinkwrap.json +2 -2
  34. package/package.json +1 -1
  35. package/dist/services/auto-fix/autoFixConfig.d.ts +0 -30
  36. package/dist/services/auto-fix/autoFixConfig.js +0 -78
  37. package/dist/services/auto-fix/autoFixHook.d.ts +0 -27
  38. package/dist/services/auto-fix/autoFixHook.js +0 -99
  39. package/dist/services/auto-fix/autoFixRunner.d.ts +0 -60
  40. package/dist/services/auto-fix/autoFixRunner.js +0 -207
  41. package/dist/services/auto-fix/index.d.ts +0 -7
  42. package/dist/services/auto-fix/index.js +0 -17
  43. package/dist/web-client/assets/index-BhGhORAX.js +0 -20
@@ -10,9 +10,11 @@ exports.SESSION_TAG_MAX_LENGTH = exports.SESSION_TAG_LIMIT = exports.SessionComp
10
10
  exports.resolveProjectPath = resolveProjectPath;
11
11
  exports.getProjectKey = getProjectKey;
12
12
  exports.isSessionMetaFile = isSessionMetaFile;
13
+ exports.prepareSessionCatalogAsync = prepareSessionCatalogAsync;
13
14
  exports.prepareSessionCompactSourceReceipt = prepareSessionCompactSourceReceipt;
14
15
  exports.advanceSessionCompactSourceReceipt = advanceSessionCompactSourceReceipt;
15
16
  exports.createSession = createSession;
17
+ exports.createSessionAsync = createSessionAsync;
16
18
  exports.saveSessionMeta = saveSessionMeta;
17
19
  exports.updateSessionEffort = updateSessionEffort;
18
20
  exports.updateSessionComposerPreferences = updateSessionComposerPreferences;
@@ -219,7 +221,6 @@ function sanitizeTraceEvent(event) {
219
221
  // ============================================================================
220
222
  // Project helpers
221
223
  // ============================================================================
222
- const MAX_CACHE_SIZE = 256;
223
224
  const SESSION_CATALOG_VERSION = 1;
224
225
  let sessionCatalogCache = null;
225
226
  function getSessionCatalogPath() {
@@ -228,63 +229,15 @@ function getSessionCatalogPath() {
228
229
  function emptySessionCatalog() {
229
230
  return { version: SESSION_CATALOG_VERSION, sessions: {} };
230
231
  }
231
- /**
232
- * Evict the oldest entries when a Map exceeds MAX_CACHE_SIZE.
233
- * Simple LRU approximation: delete the first (oldest) entries until
234
- * the map is within bounds. Called after each insertion.
235
- */
236
- function evictOldest(map, maxSize) {
237
- while (map.size > maxSize) {
238
- const firstKey = map.keys().next().value;
239
- if (firstKey !== undefined)
240
- map.delete(firstKey);
241
- else
242
- break;
243
- }
244
- }
245
- const resolvedProjectPathCache = new Map();
246
232
  /**
247
233
  * Resolve a working directory to the project identity used for session storage.
248
234
  * Git repositories share sessions from the repository root; non-git folders use
249
235
  * their real absolute path.
250
236
  */
251
237
  function resolveProjectPath(cwd = process.cwd()) {
252
- const absolute = (0, path_1.resolve)(cwd);
253
- const cached = resolvedProjectPathCache.get(absolute);
254
- if (cached) {
255
- return cached;
256
- }
257
- let resolvedPath = absolute;
258
- if ((0, fs_1.existsSync)(absolute)) {
259
- try {
260
- const root = (0, child_process_1.execFileSync)('git', ['-C', absolute, 'rev-parse', '--show-toplevel'], {
261
- encoding: 'utf-8',
262
- stdio: ['ignore', 'pipe', 'ignore'],
263
- }).trim();
264
- if (root) {
265
- resolvedPath = (0, fs_1.realpathSync)(root);
266
- resolvedProjectPathCache.set(absolute, resolvedPath);
267
- evictOldest(resolvedProjectPathCache, MAX_CACHE_SIZE);
268
- return resolvedPath;
269
- }
270
- }
271
- catch (error) {
272
- // Not a git worktree, or git is unavailable.
273
- (0, debug_log_1.debugError)('session-storage.resolveGitRoot', error, absolute);
274
- }
275
- }
276
- try {
277
- resolvedPath = (0, fs_1.realpathSync)(absolute);
278
- }
279
- catch (error) {
280
- // Broken symlink or concurrent deletion; the literal path still works
281
- // as a stable project key.
282
- (0, debug_log_1.debugError)('session-storage.realpath', error, absolute);
283
- resolvedPath = absolute;
284
- }
285
- resolvedProjectPathCache.set(absolute, resolvedPath);
286
- evictOldest(resolvedProjectPathCache, MAX_CACHE_SIZE);
287
- return resolvedPath;
238
+ // Storage helpers and Session identity must share the same invalidation
239
+ // boundary; a second permanent cache could miss a newly initialized repo.
240
+ return (0, config_dir_1.resolveProjectStoragePath)(cwd);
288
241
  }
289
242
  function getProjectKey(projectPath) {
290
243
  return (0, config_dir_1.encodeProjectPath)(resolveProjectPath(projectPath));
@@ -307,8 +260,8 @@ function getGitBranch(projectPath) {
307
260
  return undefined;
308
261
  }
309
262
  }
310
- function normalizeSessionMeta(session) {
311
- const projectPath = resolveProjectPath(session.projectPath);
263
+ function normalizeSessionMeta(session, persistedStorage) {
264
+ const projectPath = persistedStorage?.projectPath ?? resolveProjectPath(session.projectPath);
312
265
  const startTime = session.startTime ?? Date.now();
313
266
  const updatedAt = session.updatedAt ?? session.endTime ?? startTime;
314
267
  return {
@@ -321,15 +274,20 @@ function normalizeSessionMeta(session) {
321
274
  updatedAt,
322
275
  updatedAtIso: session.updatedAtIso ?? new Date(updatedAt).toISOString(),
323
276
  messageCount: session.messageCount ?? 0,
324
- historySizeBytes: session.historySizeBytes ?? computeSessionHistorySizeBytes({ id: session.id, projectPath }),
277
+ historySizeBytes: session.historySizeBytes ??
278
+ (persistedStorage
279
+ ? (safeFileSize(persistedStorage.historyPath) ?? 0)
280
+ : computeSessionHistorySizeBytes({ id: session.id, projectPath })),
325
281
  tokenCount: session.tokenCount ?? 0,
326
282
  cost: session.cost ?? 0,
327
- gitBranch: session.gitBranch ?? getGitBranch(projectPath),
283
+ // Normalization is a read-model operation. Missing optional metadata must
284
+ // not spawn Git on every read/save of a non-repository Session.
285
+ gitBranch: session.gitBranch,
328
286
  };
329
287
  }
330
- function tryNormalizeSessionMeta(session, sourcePath) {
288
+ function tryNormalizeSessionMeta(session, sourcePath, persistedStorage) {
331
289
  try {
332
- return normalizeSessionMeta(session);
290
+ return normalizeSessionMeta(session, persistedStorage);
333
291
  }
334
292
  catch (error) {
335
293
  (0, debug_log_1.debugError)('session-storage.normalizeSessionMeta', error, sourcePath);
@@ -479,6 +437,109 @@ function updateSessionCatalog(update) {
479
437
  return catalog;
480
438
  }, { waitMs: 10000 });
481
439
  }
440
+ function catalogSourceStamp(path) {
441
+ try {
442
+ const stat = (0, fs_1.statSync)(path);
443
+ return [stat.dev, stat.ino, stat.mode, stat.size, stat.mtimeMs, stat.ctimeMs].join(':');
444
+ }
445
+ catch (error) {
446
+ if (error.code === 'ENOENT')
447
+ return 'missing';
448
+ throw error;
449
+ }
450
+ }
451
+ async function scanSessionCatalogAsync() {
452
+ const catalog = emptySessionCatalog();
453
+ const stamps = new Map();
454
+ const projectsDir = (0, config_dir_1.getProjectsDir)();
455
+ stamps.set(projectsDir, catalogSourceStamp(projectsDir));
456
+ if (!(0, fs_1.existsSync)(projectsDir))
457
+ return { catalog, stamps };
458
+ let sinceYield = 0;
459
+ for (const projectKey of await fs_1.promises.readdir(projectsDir)) {
460
+ const sessionsDir = (0, path_1.join)(projectsDir, projectKey, 'sessions');
461
+ stamps.set(sessionsDir, catalogSourceStamp(sessionsDir));
462
+ if (!(0, fs_1.existsSync)(sessionsDir))
463
+ continue;
464
+ for (const file of (await fs_1.promises.readdir(sessionsDir)).filter(isSessionMetaFile)) {
465
+ const path = (0, path_1.join)(sessionsDir, file);
466
+ // Stamp before reading. A concurrent atomic replacement between these
467
+ // steps must invalidate the scan rather than bless stale bytes.
468
+ stamps.set(path, catalogSourceStamp(path));
469
+ let raw;
470
+ try {
471
+ raw = JSON.parse(await fs_1.promises.readFile(path, 'utf8'));
472
+ }
473
+ catch (error) {
474
+ (0, debug_log_1.debugError)('session-storage.scanCatalogAsync', error, path);
475
+ continue;
476
+ }
477
+ if (!raw || typeof raw.id !== 'string' || !raw.id || !raw.projectPath)
478
+ continue;
479
+ if (typeof raw.projectPath !== 'string')
480
+ continue;
481
+ // Historical metadata already owns its persisted root. Rediscovering
482
+ // Git here could migrate it, or let an unrelated broken repo deny Host
483
+ // startup. Rebuild the index without probing any historical repository.
484
+ const normalized = tryNormalizeSessionMeta(raw, path, {
485
+ projectPath: (0, path_1.resolve)(raw.projectPath),
486
+ historyPath: path.slice(0, -'.json'.length) + '.jsonl',
487
+ });
488
+ if (normalized) {
489
+ const previous = catalog.sessions[normalized.id];
490
+ if (!previous ||
491
+ (normalized.updatedAt ?? normalized.startTime) >
492
+ (previous.updatedAt ?? previous.startTime)) {
493
+ catalog.sessions[normalized.id] = normalized;
494
+ }
495
+ }
496
+ if (++sinceYield >= 16) {
497
+ sinceYield = 0;
498
+ await new Promise(resolveYield => setImmediate(resolveYield));
499
+ }
500
+ }
501
+ }
502
+ return { catalog, stamps };
503
+ }
504
+ /** Warm/rebuild the global read model without synchronous lock retry or scan. */
505
+ async function prepareSessionCatalogAsync(waitMs = 10000) {
506
+ (0, config_dir_1.ensureConfigDir)();
507
+ const path = getSessionCatalogPath();
508
+ if (readCachedSessionCatalog(path))
509
+ return;
510
+ const deadline = Date.now() + waitMs;
511
+ let scanned = await scanSessionCatalogAsync();
512
+ const scanChanged = new Error('Session Catalog sources changed during scan.');
513
+ while (true) {
514
+ try {
515
+ (0, file_lock_1.withFileLockSync)(path, () => {
516
+ // A writer may have published a newer Catalog while the scan ran.
517
+ if (readCachedSessionCatalog(path))
518
+ return;
519
+ for (const [sourcePath, stamp] of scanned.stamps) {
520
+ if (catalogSourceStamp(sourcePath) !== stamp)
521
+ throw scanChanged;
522
+ }
523
+ writeSessionCatalog(path, scanned.catalog);
524
+ }, { waitMs: 0 });
525
+ return;
526
+ }
527
+ catch (error) {
528
+ if (Date.now() >= deadline)
529
+ throw error;
530
+ if (error === scanChanged) {
531
+ scanned = await scanSessionCatalogAsync();
532
+ }
533
+ else if (error instanceof Error &&
534
+ error.message === `Timed out waiting for file lock ${path}.lock`) {
535
+ await new Promise(resolveWait => setTimeout(resolveWait, 10));
536
+ }
537
+ else {
538
+ throw error;
539
+ }
540
+ }
541
+ }
542
+ }
482
543
  function parseHarnessSidecarFile(path) {
483
544
  try {
484
545
  const content = (0, fs_1.readFileSync)(path, 'utf-8');
@@ -913,8 +974,9 @@ function computeSessionHistorySizeBytes(session) {
913
974
  // ============================================================================
914
975
  /**
915
976
  * 创建新会话
977
+ * @param options Optional pre-probed Git metadata. Supplying this object skips synchronous Git.
916
978
  */
917
- function createSession(projectPath, model) {
979
+ function createSession(projectPath, model, options) {
918
980
  (0, config_dir_1.ensureConfigDir)();
919
981
  const canonicalProjectPath = resolveProjectPath(projectPath);
920
982
  const now = Date.now();
@@ -929,13 +991,87 @@ function createSession(projectPath, model) {
929
991
  updatedAt: now,
930
992
  updatedAtIso: new Date(now).toISOString(),
931
993
  messageCount: 0,
932
- gitBranch: getGitBranch(canonicalProjectPath),
994
+ // Interactive Hosts can supply an asynchronously probed branch. Passing
995
+ // options (including an unavailable branch) deliberately skips the legacy
996
+ // synchronous Git probe; CLI/TUI callers retain their existing behavior.
997
+ gitBranch: options ? options.gitBranch : getGitBranch(canonicalProjectPath),
933
998
  tokenCount: 0,
934
999
  cost: 0,
935
1000
  };
936
1001
  saveSessionMeta(session);
937
1002
  return session;
938
1003
  }
1004
+ /**
1005
+ * Web creation waits for cross-process metadata/catalog locks asynchronously.
1006
+ * Both locks are acquired before any Session bytes are written. The original
1007
+ * synchronous lock owner/recovery and atomic publication remain authoritative.
1008
+ */
1009
+ async function createSessionAsync(projectPath, model, options = {}) {
1010
+ (0, config_dir_1.ensureConfigDir)();
1011
+ const deadline = Date.now() + (options.waitMs ?? 10000);
1012
+ options.beforeCommit?.();
1013
+ const storageIdentity = await (0, paths_1.resolveProjectStorageIdentityAsync)(projectPath);
1014
+ const canonicalProjectPath = storageIdentity.canonicalPath;
1015
+ await prepareSessionCatalogAsync(Math.max(0, deadline - Date.now()));
1016
+ const now = Date.now();
1017
+ const session = {
1018
+ id: (0, crypto_1.randomUUID)(),
1019
+ projectPath: canonicalProjectPath,
1020
+ projectKey: (0, config_dir_1.encodeProjectPath)(canonicalProjectPath),
1021
+ cwd: (0, path_1.resolve)(projectPath),
1022
+ model,
1023
+ startTime: now,
1024
+ createdAt: new Date(now).toISOString(),
1025
+ updatedAt: now,
1026
+ updatedAtIso: new Date(now).toISOString(),
1027
+ messageCount: 0,
1028
+ historySizeBytes: 0,
1029
+ gitBranch: options.gitBranch,
1030
+ tokenCount: 0,
1031
+ cost: 0,
1032
+ ...(options.name?.trim() ? { name: options.name.trim() } : {}),
1033
+ };
1034
+ const sessionsDir = (0, paths_1.ensureResolvedProjectDir)(storageIdentity);
1035
+ const metaPath = (0, path_1.join)(sessionsDir, `${session.id}.json`);
1036
+ const catalogPath = getSessionCatalogPath();
1037
+ const lockTimeoutMessages = new Set([metaPath, catalogPath].map(path => `Timed out waiting for file lock ${path}.lock`));
1038
+ while (true) {
1039
+ options.beforeCommit?.();
1040
+ (0, paths_1.assertProjectStorageIdentity)(storageIdentity);
1041
+ try {
1042
+ return (0, file_lock_1.withFileLockSync)(metaPath, () => (0, file_lock_1.withFileLockSync)(catalogPath, () => {
1043
+ options.beforeCommit?.();
1044
+ (0, paths_1.assertProjectStorageIdentity)(storageIdentity);
1045
+ const catalog = readSessionCatalogFile(catalogPath);
1046
+ if (!catalog) {
1047
+ throw new Error('Session Catalog changed before creation. Retry.');
1048
+ }
1049
+ if ((0, fs_1.existsSync)(metaPath))
1050
+ throw new Error('Session identity already exists.');
1051
+ (0, atomic_write_1.atomicWriteFileSync)(metaPath, JSON.stringify(session, null, 2), { mode: 0o600 });
1052
+ try {
1053
+ catalog.sessions[session.id] = session;
1054
+ writeSessionCatalog(catalogPath, catalog);
1055
+ }
1056
+ catch (error) {
1057
+ // This identity was not exposed to a caller or Catalog yet.
1058
+ // Roll back its metadata while both locks are still ours.
1059
+ (0, fs_1.unlinkSync)(metaPath);
1060
+ throw error;
1061
+ }
1062
+ return session;
1063
+ }, { waitMs: 0 }), { waitMs: 0 });
1064
+ }
1065
+ catch (error) {
1066
+ if (!(error instanceof Error) ||
1067
+ !lockTimeoutMessages.has(error.message) ||
1068
+ Date.now() >= deadline) {
1069
+ throw error;
1070
+ }
1071
+ await new Promise(resolveWait => setTimeout(resolveWait, 10));
1072
+ }
1073
+ }
1074
+ }
939
1075
  /**
940
1076
  * 保存会话元数据
941
1077
  */
@@ -471,6 +471,14 @@ export interface WebBootstrapV1 {
471
471
  readonly workspace: string;
472
472
  readonly configured: boolean;
473
473
  readonly activeSessionId: string | null;
474
+ /**
475
+ * v0.3.24 — the Host's latest durable event cursor at bootstrap time. Clients
476
+ * use it as the replay anchor: durable envelopes at or below this cursor are
477
+ * already reflected by the baseline and must not be re-applied (a reloaded
478
+ * tab otherwise replays pre-baseline `workbench_state` broadcasts that would
479
+ * clear its restored foreground Session). Older clients ignore it.
480
+ */
481
+ readonly eventCursor?: number;
474
482
  readonly settings: WebSettingsSnapshotV1;
475
483
  readonly capabilities: {
476
484
  readonly goal: true;
@@ -5,7 +5,9 @@ exports.pageItems = pageItems;
5
5
  exports.pageCollectionItems = pageCollectionItems;
6
6
  exports.hasProjectSignal = hasProjectSignal;
7
7
  const crypto_1 = require("crypto");
8
+ const child_process_1 = require("child_process");
8
9
  const fs_1 = require("fs");
10
+ const paths_1 = require("../product/paths");
9
11
  const promises_1 = require("fs/promises");
10
12
  const path_1 = require("path");
11
13
  const agent_runtime_controller_1 = require("../runtime/agent-runtime-controller");
@@ -156,6 +158,8 @@ class WebWorkbenchController {
156
158
  }
157
159
  static async create(options) {
158
160
  const workbench = new WebWorkbenchController(options);
161
+ await (0, paths_1.resolveProjectStoragePathAsync)(workbench.workspaceValue);
162
+ await (0, session_storage_1.prepareSessionCatalogAsync)();
159
163
  workbench.initializeWorkspaceRegistry();
160
164
  await workbench.installRuntime(workbench.workspaceValue);
161
165
  return workbench;
@@ -182,6 +186,7 @@ class WebWorkbenchController {
182
186
  workspace: this.workspaceValue,
183
187
  configured: this.runtimeValue.isConfigured,
184
188
  activeSessionId: this.runtimeValue.getSession()?.id ?? null,
189
+ eventCursor: this.eventHub.snapshot().latest,
185
190
  settings: this.settings(),
186
191
  capabilities: {
187
192
  goal: true,
@@ -1267,9 +1272,32 @@ class WebWorkbenchController {
1267
1272
  // A new Session inherits the durable default; an override on another
1268
1273
  // Session actor must never leak into it. Creation does not activate or
1269
1274
  // close any actor; foreground selection belongs to the browser tab.
1270
- const session = (0, session_storage_1.createSession)(this.workspaceValue, this.settings().sections.defaults.model.effectiveValue);
1275
+ const workspace = this.workspaceValue;
1276
+ const creationContext = context ?? {
1277
+ workspaceId: this.activeWorkspaceEntry().id,
1278
+ expectedContextRevision: this.contextRevisionValue,
1279
+ };
1280
+ const gitBranch = await probeSessionGitBranch(workspace);
1281
+ // Git is optional metadata, but its asynchronous boundary must not allow a
1282
+ // stale browser to create a Session in a newly selected Workspace.
1283
+ this.assertContextGuard(creationContext);
1284
+ let named;
1285
+ try {
1286
+ await (0, paths_1.resolveProjectStoragePathAsync)(workspace);
1287
+ this.assertContextGuard(creationContext);
1288
+ named = await (0, session_storage_1.createSessionAsync)(workspace, this.settings().sections.defaults.model.effectiveValue, {
1289
+ gitBranch,
1290
+ name,
1291
+ beforeCommit: () => this.assertContextGuard(creationContext),
1292
+ });
1293
+ }
1294
+ catch (error) {
1295
+ if (error instanceof paths_1.ProjectStorageIdentityUnavailableError) {
1296
+ throw new errors_1.WebWorkbenchError(503, error.message, error.code);
1297
+ }
1298
+ throw error;
1299
+ }
1271
1300
  (0, global_config_1.incrementSessionCount)();
1272
- const named = name?.trim() ? ((0, session_storage_1.renameSession)(session.id, name.trim()) ?? session) : session;
1273
1301
  this.sessionRegistry.summary(this.sessionActorKey(named));
1274
1302
  return (0, protocol_1.projectSessionSummary)(named);
1275
1303
  }
@@ -2154,6 +2182,9 @@ class WebWorkbenchController {
2154
2182
  }
2155
2183
  }
2156
2184
  async installRuntime(workspace, publishState = true) {
2185
+ const storageRoot = await (0, paths_1.resolveProjectStoragePathAsync)(workspace);
2186
+ if (storageRoot !== workspace)
2187
+ await (0, paths_1.resolveProjectStoragePathAsync)(storageRoot);
2157
2188
  // Every control-plane install reloads the Workspace catalog, provider
2158
2189
  // wiring, Skills and MCP adapters, so this counter proxies "catalog
2159
2190
  // reload" for the runtime-ownership diagnostics matrix.
@@ -2960,6 +2991,19 @@ function requireSession(sessionId) {
2960
2991
  throw new errors_1.WebWorkbenchError(404, 'Session was not found.');
2961
2992
  return session;
2962
2993
  }
2994
+ /** Optional metadata must neither block the Host event loop nor delay creation indefinitely. */
2995
+ function probeSessionGitBranch(workspace) {
2996
+ return new Promise(resolveBranch => {
2997
+ (0, child_process_1.execFile)('git', ['-C', workspace, 'rev-parse', '--abbrev-ref', 'HEAD'], {
2998
+ encoding: 'utf8',
2999
+ timeout: 250,
3000
+ killSignal: 'SIGKILL',
3001
+ maxBuffer: 4096,
3002
+ env: process.env,
3003
+ windowsHide: true,
3004
+ }, (error, stdout) => resolveBranch(error ? undefined : stdout.trim() || undefined));
3005
+ });
3006
+ }
2963
3007
  function mcpServerIds(config) {
2964
3008
  const ids = new Set();
2965
3009
  for (const envelope of [config.mcpServers, config.servers, config.orion?.mcp?.servers]) {
@@ -1 +1 @@
1
- import{r as a,j as e,I as D,s as be,C as ye,W as ge}from"./index-BhGhORAX.js";import{u as Ce,R as Ne}from"./useAutoNotice-BLADeFFj.js";function Le({workspaceId:s,refreshEpoch:o,actions:l,navigatorWidthPx:r,onNavigatorWidthCommit:d,revealRequest:c}){const[f,m]=a.useState({}),[x,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(""),[he,W]=a.useState(!1),h=a.useRef(0),C=a.useRef(0),ee=a.useRef(null),E=async(t,n=!1,y=h.current)=>{if(y!==h.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(y!==h.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(y!==h.current)return;if(n&&U(u)){$("目录已变化,已重新载入第一页。"),E(t,!1,y);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=h.current)=>{try{const n=await l.gitStatus();if(t!==h.current)return;_(Se(n)),A(n.nextCursor?"Git 装饰仅显示首批 200 个变更;在 Git 面板继续分页查看。":"")}catch(n){if(t!==h.current)return;_({}),A(`Git 状态装饰不可用:${z(n)}`)}},te=a.useRef(s);a.useEffect(()=>{const t=h.current+1;h.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&&b(n,!1,!1,t))},[o,s]);const se=a.useRef(null),b=async(t,n=!1,y=!1,p=h.current,g=C.current+1)=>{if(p===h.current&&!(!t.readable||t.sensitive||X(t))){C.current=g,N(t),K(""),n||(B(""),G(null),P(!1),R("view"),w(""),S(!1)),y||V();try{const u=await l.readFileContent(t.id,n?M??void 0:void 0);if(p!==h.current||g!==C.current)return;P(u.binary),B(v=>n?`${v}${u.content??""}`:u.content??""),G(u.nextCursor),me(u.revision),y&&$("文件已变化,已从第一页重新载入。")}catch(u){if(p!==h.current||g!==C.current)return;if(n&&U(u)){await b(t,!1,!0,p,g);return}K(z(u))}}},re=De({selected:i,binary:Y,hasMorePages:M!==null}),xe=()=>{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 b(i,!1,!0,h.current,C.current+1),$("已保存文件。","success"),Q()}catch(t){U(t)?S(!0):$(z(t),"error")}finally{q(!1)}}},ae=t=>{const n=new Set(x);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,b({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=>{b(n)}},t.id))})}):e.jsx(le,{parentId:"workspace-root",depth:0,directories:f,expanded:x,selectedId:i?.id,gitDecorations:Z,onToggle:ae,onSelect:t=>{b(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:xe,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:()=>{b(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:be(F)})]}),M&&O==="view"?e.jsx("button",{type:"button",className:"secondary-button",onClick:()=>{b(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(ye,{open:he,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:x}){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:x}):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:()=>x(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,x=X(s);return e.jsxs("button",{type:"button",className:`file-node ${r?"selected":""} ${m?"blocked":""}`,style:{paddingInlineStart:`${8+o*15}px`},"aria-expanded":x?l:void 0,"aria-label":`${we(s)} ${s.name}${m?",不可读取":""}${d.length?`,Git ${d.join("、")}`:""}`,onClick:()=>x?c(s.id):f(s),disabled:m,children:[e.jsx(D,{name:x?"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.hasWorktree)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 x=1;x<m.length;x+=1){const j=m.slice(0,x).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};
1
+ import{r as a,j as e,I as D,s as be,C as ye,W as ge}from"./index-DZOlUgt6.js";import{u as Ce,R as Ne}from"./useAutoNotice-Ylj5bfSH.js";function Le({workspaceId:s,refreshEpoch:o,actions:l,navigatorWidthPx:r,onNavigatorWidthCommit:d,revealRequest:c}){const[f,m]=a.useState({}),[x,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(""),[he,W]=a.useState(!1),h=a.useRef(0),C=a.useRef(0),ee=a.useRef(null),E=async(t,n=!1,y=h.current)=>{if(y!==h.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(y!==h.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(y!==h.current)return;if(n&&U(u)){$("目录已变化,已重新载入第一页。"),E(t,!1,y);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=h.current)=>{try{const n=await l.gitStatus();if(t!==h.current)return;_(Se(n)),A(n.nextCursor?"Git 装饰仅显示首批 200 个变更;在 Git 面板继续分页查看。":"")}catch(n){if(t!==h.current)return;_({}),A(`Git 状态装饰不可用:${z(n)}`)}},te=a.useRef(s);a.useEffect(()=>{const t=h.current+1;h.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&&b(n,!1,!1,t))},[o,s]);const se=a.useRef(null),b=async(t,n=!1,y=!1,p=h.current,g=C.current+1)=>{if(p===h.current&&!(!t.readable||t.sensitive||X(t))){C.current=g,N(t),K(""),n||(B(""),G(null),P(!1),R("view"),w(""),S(!1)),y||V();try{const u=await l.readFileContent(t.id,n?M??void 0:void 0);if(p!==h.current||g!==C.current)return;P(u.binary),B(v=>n?`${v}${u.content??""}`:u.content??""),G(u.nextCursor),me(u.revision),y&&$("文件已变化,已从第一页重新载入。")}catch(u){if(p!==h.current||g!==C.current)return;if(n&&U(u)){await b(t,!1,!0,p,g);return}K(z(u))}}},re=De({selected:i,binary:Y,hasMorePages:M!==null}),xe=()=>{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 b(i,!1,!0,h.current,C.current+1),$("已保存文件。","success"),Q()}catch(t){U(t)?S(!0):$(z(t),"error")}finally{q(!1)}}},ae=t=>{const n=new Set(x);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,b({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=>{b(n)}},t.id))})}):e.jsx(le,{parentId:"workspace-root",depth:0,directories:f,expanded:x,selectedId:i?.id,gitDecorations:Z,onToggle:ae,onSelect:t=>{b(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:xe,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:()=>{b(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:be(F)})]}),M&&O==="view"?e.jsx("button",{type:"button",className:"secondary-button",onClick:()=>{b(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(ye,{open:he,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:x}){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:x}):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:()=>x(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,x=X(s);return e.jsxs("button",{type:"button",className:`file-node ${r?"selected":""} ${m?"blocked":""}`,style:{paddingInlineStart:`${8+o*15}px`},"aria-expanded":x?l:void 0,"aria-label":`${we(s)} ${s.name}${m?",不可读取":""}${d.length?`,Git ${d.join("、")}`:""}`,onClick:()=>x?c(s.id):f(s),disabled:m,children:[e.jsx(D,{name:x?"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.hasWorktree)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 x=1;x<m.length;x+=1){const j=m.slice(0,x).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};