weapp-ide-cli 6.2.0 → 6.3.0

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.
@@ -1,16 +1,16 @@
1
- import { a as assertWechatDevtoolsPort, c as withPowerShellUtf8Output, i as assertWechatDevtoolsHost, o as resolveWechatDevtoolsInstallationRoot, r as resolveWechatDevtoolsTarget, s as resolveWechatInspectionTimeout } from "./sessionStore-zdzy2UFM.js";
1
+ import { E as withPowerShellUtf8Output, T as inspectWechatDevtoolsInstallation, g as resolveWechatInspectionTimeout, h as resolveWechatDevtoolsInstallationRoot, m as assertWechatDevtoolsPort, p as assertWechatDevtoolsHost, u as resolveWechatDevtoolsTarget } from "./automatorProject-DjYLqMj6.js";
2
2
  import path from "node:path";
3
3
  import { TextDecoder, debuglog, isDeepStrictEqual, stripVTControlCharacters } from "node:util";
4
4
  import { assertMachineE2ELeaseRecoveryScope, mutateLease, withMachineE2ELease, withMachineE2ELeaseRecoveryOperation } from "@weapp-vite/devtools-runtime";
5
5
  import { execa } from "execa";
6
- import { createHash, randomUUID } from "node:crypto";
7
6
  import fs from "node:fs/promises";
8
7
  import process from "node:process";
8
+ import { createHash, randomUUID } from "node:crypto";
9
9
  import { Buffer } from "node:buffer";
10
10
  import net from "node:net";
11
11
  import { setTimeout } from "node:timers/promises";
12
12
  import { z } from "zod";
13
- import { performance } from "node:perf_hooks";
13
+ import { performance as performance$1 } from "node:perf_hooks";
14
14
  //#region src/cli/agentStart.ts
15
15
  /** 提取独立的完整 JSON 对象,普通日志和嵌套对象不能替代命令响应。 */
16
16
  function readResponseObjects(stdout) {
@@ -91,6 +91,9 @@ async function startWechatIdeAgent(options) {
91
91
  String(options.port)
92
92
  ];
93
93
  if (options.trustProject === true) argv.push("--trust-project");
94
+ options.signal?.throwIfAborted();
95
+ await options.onBeforeDispatch?.();
96
+ options.signal?.throwIfAborted();
94
97
  const started = parseAgentStartResponse((await execa(target.cliPath, argv, {
95
98
  cancelSignal: options.signal,
96
99
  timeout,
@@ -114,34 +117,151 @@ async function startWechatIdeAgent(options) {
114
117
  });
115
118
  }
116
119
  //#endregion
117
- //#region src/devtoolsProjectOwnership/host.ts
118
- const inspectionOptions$1 = {
120
+ //#region src/devtoolsProjectOwnership/installationExit/darwinKernelPaths.ts
121
+ const inspectionTimeoutMs$1 = 1e4;
122
+ const kernelPathsScript = `
123
+ ObjC.import('Foundation');
124
+ ObjC.bindFunction('malloc', ['void *', ['unsigned long']]);
125
+ ObjC.bindFunction('free', ['void', ['void *']]);
126
+ ObjC.bindFunction('proc_pidpath', ['int', ['int', 'void *', 'uint32_t']]);
127
+ ObjC.bindFunction('strchr', ['char *', ['void *', 'int']]);
128
+ function run(argv) {
129
+ var pids = JSON.parse(argv[0]);
130
+ var buffer = $.malloc(4096);
131
+ if (!buffer) throw new Error('Cannot allocate process path buffer.');
132
+ try {
133
+ return JSON.stringify(pids.map(function(pid) {
134
+ var count = $.proc_pidpath(pid, buffer, 4096);
135
+ return { pid: pid, executable: count > 0 ? $.strchr(buffer, 47) : null };
136
+ }));
137
+ } finally { $.free(buffer); }
138
+ }
139
+ `;
140
+ /** 在 JSON 边界严格核验内核响应,不让进程身份查询依赖 schema 运行时。 */
141
+ function parseKernelPaths(stdout) {
142
+ const parsed = JSON.parse(stdout);
143
+ if (!Array.isArray(parsed)) throw new TypeError("Invalid kernel process paths.");
144
+ return parsed.map((entry) => {
145
+ if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 2 || !("pid" in entry) || typeof entry.pid !== "number" || !Number.isSafeInteger(entry.pid) || entry.pid <= 0 || !("executable" in entry) || entry.executable !== null && typeof entry.executable !== "string") throw new TypeError("Invalid kernel process path entry.");
146
+ return {
147
+ pid: entry.pid,
148
+ executable: entry.executable
149
+ };
150
+ });
151
+ }
152
+ /** 系统 JXA/libproc 不可用时明确拒绝核验,不安装工具或以进程标题降级。 */
153
+ async function readDarwinKernelPaths(pids, timeoutMs = inspectionTimeoutMs$1) {
154
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error("Installation-exit recovery cannot inspect kernel executable paths.");
155
+ const result = await execa("/usr/bin/osascript", [
156
+ "-l",
157
+ "JavaScript",
158
+ "-e",
159
+ kernelPathsScript,
160
+ JSON.stringify(pids)
161
+ ], {
162
+ timeout: Math.min(inspectionTimeoutMs$1, timeoutMs),
163
+ reject: false,
164
+ env: { LC_ALL: "C" }
165
+ });
166
+ if (result.exitCode !== 0 || result.timedOut || result.signal || result.stderr?.trim()) throw new Error("Installation-exit recovery cannot inspect kernel executable paths.");
167
+ let parsed;
168
+ try {
169
+ parsed = parseKernelPaths(result.stdout);
170
+ } catch {
171
+ throw new Error("Installation-exit recovery received incomplete kernel executable paths.");
172
+ }
173
+ const entries = new Map(parsed.map((entry) => [entry.pid, entry.executable]));
174
+ if (entries.size !== pids.length || parsed.length !== pids.length || pids.some((pid) => !entries.has(pid))) throw new Error("Installation-exit recovery received incomplete kernel process identities.");
175
+ return entries;
176
+ }
177
+ //#endregion
178
+ //#region src/devtoolsProjectOwnership/darwinProcessIdentity.ts
179
+ const inspectionTimeoutMs = 3e3;
180
+ function invalidIdentity$2() {
181
+ return /* @__PURE__ */ new Error("Managed DevTools process identity could not be verified; no project was closed.");
182
+ }
183
+ async function readStarted(pids, remaining) {
184
+ const result = await execa("/bin/ps", [
185
+ "-p",
186
+ pids.join(","),
187
+ "-o",
188
+ "pid=,lstart="
189
+ ], {
190
+ timeout: remaining(),
191
+ reject: false,
192
+ env: { LC_ALL: "C" }
193
+ });
194
+ remaining();
195
+ if (result.timedOut || result.signal || result.stderr?.trim() || result.exitCode === 0 && !result.stdout.trim() || result.exitCode !== 0 && !(result.exitCode === 1 && !result.stdout.trim())) throw invalidIdentity$2();
196
+ const started = /* @__PURE__ */ new Map();
197
+ for (const line of result.stdout.split(/\r?\n/).filter((line) => line.trim())) {
198
+ const match = /^\s*(\d+)\s+(\w{3}\s+\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s*$/.exec(line);
199
+ const pid = Number(match?.[1]);
200
+ if (!match || !pids.includes(pid) || started.has(pid)) throw invalidIdentity$2();
201
+ started.set(pid, match[2].replace(/\s+/g, " "));
202
+ }
203
+ return started;
204
+ }
205
+ /** 批量读取内核路径,并以前后启动时间绑定 PID;进程标题变化不改变身份。 */
206
+ async function readDarwinManagedProcessIdentities(pids) {
207
+ if (pids.some((pid) => !Number.isSafeInteger(pid) || pid <= 0)) throw invalidIdentity$2();
208
+ const requested = [...new Set(pids)];
209
+ const identities = new Map(requested.map((pid) => [pid, void 0]));
210
+ if (!requested.length) return identities;
211
+ const deadline = performance.now() + inspectionTimeoutMs;
212
+ const remaining = () => {
213
+ const timeout = Math.floor(deadline - performance.now());
214
+ if (timeout <= 0) throw invalidIdentity$2();
215
+ return timeout;
216
+ };
217
+ const before = await readStarted(requested, remaining);
218
+ if (!before.size) return identities;
219
+ const paths = await readDarwinKernelPaths([...before.keys()], remaining());
220
+ const after = await readStarted(requested, remaining);
221
+ for (const pid of requested) {
222
+ const started = after.get(pid);
223
+ if (!started) continue;
224
+ const executable = paths.get(pid);
225
+ if (before.get(pid) !== started || !executable?.startsWith("/")) throw invalidIdentity$2();
226
+ identities.set(pid, {
227
+ pid,
228
+ started,
229
+ executable
230
+ });
231
+ }
232
+ remaining();
233
+ return identities;
234
+ }
235
+ //#endregion
236
+ //#region src/devtoolsProjectOwnership/processIdentity.ts
237
+ const inspectionOptions$2 = {
119
238
  timeout: 3e3,
120
239
  reject: false,
121
240
  windowsHide: true
122
241
  };
123
- const windowsInspectionOptions = {
124
- ...inspectionOptions$1,
242
+ const windowsInspectionOptions$1 = {
243
+ ...inspectionOptions$2,
125
244
  timeout: resolveWechatInspectionTimeout("win32")
126
245
  };
127
- function invalidIdentity(result, timeout) {
246
+ function invalidIdentity$1(result, timeout) {
128
247
  const details = result ? ` Inspection: exitCode=${result.exitCode ?? "none"}, signal=${result.signal ?? "none"}, timedOut=${result.timedOut ?? false}, timeoutMs=${timeout}.` : "";
129
248
  return /* @__PURE__ */ new Error(`Managed DevTools process identity could not be verified; no project was closed.${details}`);
130
249
  }
131
250
  /** PID 必须连同启动时间和可执行文件核验;进程号复用不继承所有权。 */
132
251
  async function readManagedProcessIdentity(pid, platform = process.platform) {
133
- if (!Number.isSafeInteger(pid) || pid <= 0) throw invalidIdentity();
252
+ if (!Number.isSafeInteger(pid) || pid <= 0) throw invalidIdentity$1();
253
+ if (platform === "darwin") return (await readDarwinManagedProcessIdentities([pid])).get(pid);
134
254
  if (platform === "win32") {
135
255
  const result = await execa("powershell.exe", [
136
256
  "-NoProfile",
137
257
  "-NonInteractive",
138
258
  "-Command",
139
259
  withPowerShellUtf8Output(`$ErrorActionPreference='Stop'; Get-CimInstance Win32_Process -Filter "ProcessId=${pid}" | Select-Object ProcessId,ExecutablePath,@{Name='Started';Expression={$_.CreationDate.ToUniversalTime().ToString('o')}} | ConvertTo-Json -Compress`)
140
- ], windowsInspectionOptions);
141
- if (result.exitCode !== 0) throw invalidIdentity(result, windowsInspectionOptions.timeout);
260
+ ], windowsInspectionOptions$1);
261
+ if (result.exitCode !== 0) throw invalidIdentity$1(result, windowsInspectionOptions$1.timeout);
142
262
  if (!result.stdout.trim()) return;
143
263
  const value = JSON.parse(result.stdout);
144
- if (!value || typeof value !== "object" || !("ProcessId" in value) || value.ProcessId !== pid || !("ExecutablePath" in value) || typeof value.ExecutablePath !== "string" || !value.ExecutablePath || !("Started" in value) || typeof value.Started !== "string" || !value.Started) throw invalidIdentity();
264
+ if (!value || typeof value !== "object" || !("ProcessId" in value) || value.ProcessId !== pid || !("ExecutablePath" in value) || typeof value.ExecutablePath !== "string" || !value.ExecutablePath || !("Started" in value) || typeof value.Started !== "string" || !value.Started) throw invalidIdentity$1();
145
265
  return {
146
266
  pid,
147
267
  executable: value.ExecutablePath,
@@ -151,17 +271,18 @@ async function readManagedProcessIdentity(pid, platform = process.platform) {
151
271
  if (platform === "linux") try {
152
272
  const [executable, stat] = await Promise.all([fs.readlink(`/proc/${pid}/exe`), fs.readFile(`/proc/${pid}/stat`, "utf8")]);
153
273
  const boot = await fs.readFile("/proc/sys/kernel/random/boot_id", "utf8").catch(() => {
154
- throw invalidIdentity();
274
+ throw invalidIdentity$1();
155
275
  });
156
276
  const started = stat.slice(stat.lastIndexOf(")") + 2).split(/\s+/)[19];
157
- if (!started || !/^\d+$/.test(started) || !boot.trim()) throw invalidIdentity();
277
+ if (!started || !/^\d+$/.test(started) || !boot.trim()) throw invalidIdentity$1();
158
278
  return {
159
279
  pid,
160
280
  executable,
161
281
  started: `${boot.trim()}:${started}`
162
282
  };
163
283
  } catch (error) {
164
- if (error.code === "ENOENT") return;
284
+ const code = error.code;
285
+ if (code === "ENOENT" || code === "ESRCH") return;
165
286
  throw error;
166
287
  }
167
288
  const result = await execa("ps", [
@@ -172,12 +293,12 @@ async function readManagedProcessIdentity(pid, platform = process.platform) {
172
293
  "-o",
173
294
  "comm="
174
295
  ], {
175
- ...inspectionOptions$1,
296
+ ...inspectionOptions$2,
176
297
  env: { LC_ALL: "C" }
177
298
  });
178
299
  if (result.exitCode === 1 && !result.stdout.trim()) return;
179
300
  const match = /^(\w{3}\s+\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s+(\S[^\r\n]*)$/.exec(result.stdout.trim());
180
- if (result.exitCode !== 0 || !match) throw invalidIdentity();
301
+ if (result.exitCode !== 0 || !match) throw invalidIdentity$1();
181
302
  return {
182
303
  pid,
183
304
  started: match[1].replace(/\s+/g, " "),
@@ -187,6 +308,21 @@ async function readManagedProcessIdentity(pid, platform = process.platform) {
187
308
  function sameManagedProcess(first, second) {
188
309
  return first.pid === second.pid && first.started === second.started && first.executable === second.executable;
189
310
  }
311
+ //#endregion
312
+ //#region src/devtoolsProjectOwnership/host.ts
313
+ const inspectionOptions$1 = {
314
+ timeout: 3e3,
315
+ reject: false,
316
+ windowsHide: true
317
+ };
318
+ const windowsInspectionOptions = {
319
+ ...inspectionOptions$1,
320
+ timeout: resolveWechatInspectionTimeout("win32")
321
+ };
322
+ function invalidIdentity(result, timeout) {
323
+ const details = result ? ` Inspection: exitCode=${result.exitCode ?? "none"}, signal=${result.signal ?? "none"}, timedOut=${result.timedOut ?? false}, timeoutMs=${timeout}.` : "";
324
+ return /* @__PURE__ */ new Error(`Managed DevTools process identity could not be verified; no project was closed.${details}`);
325
+ }
190
326
  async function listenerPid(port, platform = process.platform) {
191
327
  const result = platform === "win32" ? await execa("powershell.exe", [
192
328
  "-NoProfile",
@@ -214,7 +350,7 @@ async function inspectManagedProjectHost(target, port, platform = process.platfo
214
350
  return identity;
215
351
  }
216
352
  async function assertManagedInstallation(target) {
217
- const current = await resolveWechatDevtoolsTarget({ cliPath: target.cliPath });
353
+ const current = await inspectWechatDevtoolsInstallation(target.cliPath);
218
354
  if (current.installationId !== target.installationId || current.appPath !== target.appPath || current.cliPath !== target.cliPath || current.profileDir !== target.profileDir || current.version !== target.version || current.channel !== target.channel) throw new Error("Managed DevTools installation changed; no project was closed.");
219
355
  }
220
356
  function isManagedPortClosed(port) {
@@ -255,7 +391,8 @@ const installationExitRecoverySchema = z.object({
255
391
  state: z.enum([
256
392
  "starting",
257
393
  "unconfirmed",
258
- "failed"
394
+ "failed",
395
+ "borrowed"
259
396
  ]),
260
397
  error: z.string().optional(),
261
398
  updatedAt: z.string().min(1),
@@ -276,12 +413,6 @@ const installationExitRecoverySchema = z.object({
276
413
  }).strict()
277
414
  }).strict();
278
415
  //#endregion
279
- //#region src/devtoolsProjectOwnership/journal/constants.ts
280
- /** 受管项目日志的环境变量名称;基础设施预检可在构建包之前读取。 */
281
- const MANAGED_PROJECT_JOURNAL_ENV = "WEAPP_IDE_MANAGED_PROJECT_JOURNAL";
282
- /** 仅供显式受管任务声明双项目窗口验收;不允许无限扩大并行窗口。 */
283
- const MANAGED_PROJECT_MAX_WINDOWS_ENV = "WEAPP_IDE_MANAGED_PROJECT_MAX_WINDOWS";
284
- //#endregion
285
416
  //#region src/devtoolsProjectOwnership/journal/scope.ts
286
417
  const markerName = ".ownership-scope";
287
418
  const scopeSchema = z.object({
@@ -467,7 +598,7 @@ async function readWindowsJournalWriterIdentity() {
467
598
  const timeout = resolveWechatInspectionTimeout("win32");
468
599
  const trace = debug.enabled;
469
600
  const command = windowsSelfIdentityCommand(process.pid);
470
- const started = performance.now();
601
+ const started = performance$1.now();
471
602
  const query = execa("powershell.exe", [
472
603
  "-NoProfile",
473
604
  "-NonInteractive",
@@ -481,14 +612,14 @@ async function readWindowsJournalWriterIdentity() {
481
612
  stripFinalNewline: !trace
482
613
  });
483
614
  if (trace) {
484
- const report = (phase) => debug("phase=%s elapsedMs=%s", phase, (performance.now() - started).toFixed(1));
615
+ const report = (phase) => debug("phase=%s elapsedMs=%s", phase, (performance$1.now() - started).toFixed(1));
485
616
  report("launch");
486
617
  query.nodeChildProcess.once("spawn", () => report("spawn"));
487
618
  query.stderr?.on("data", observeWindowsSelfIdentityPhases(report));
488
619
  }
489
620
  const result = await query;
490
621
  const stderr = trace ? stripWindowsSelfIdentityPhases(result.stderr) : result.stderr;
491
- if (trace) debug("phase=settled elapsedMs=%s exitCode=%s signal=%s timedOut=%s", (performance.now() - started).toFixed(1), result.exitCode ?? "none", result.signal ?? "none", result.timedOut ?? false);
622
+ if (trace) debug("phase=settled elapsedMs=%s exitCode=%s signal=%s timedOut=%s", (performance$1.now() - started).toFixed(1), result.exitCode ?? "none", result.signal ?? "none", result.timedOut ?? false);
492
623
  if (result.exitCode !== 0 || result.failed || result.timedOut || result.signal || stderr.trim()) {
493
624
  const reason = /^WEAPP_JOURNAL_SELF_V1:error:([\w.]+):-?\d+\r?\n?$/.exec(stderr)?.[1] ?? "unconfirmed";
494
625
  throw new Error(`Managed journal writer identity could not be verified; the journal remains unchanged. Inspection: exitCode=${result.exitCode ?? "none"}, signal=${result.signal ?? "none"}, timedOut=${result.timedOut ?? false}, timeoutMs=${timeout}, reason=${reason}.`);
@@ -513,6 +644,26 @@ function readManagedJournalWriterIdentity() {
513
644
  return currentIdentity$1;
514
645
  }
515
646
  //#endregion
647
+ //#region src/devtoolsProjectOwnership/snapshot/schema.ts
648
+ const managedSnapshotSchema = z.object({
649
+ protocol: z.literal("wechat-devtools-project-snapshot-v1"),
650
+ sourceProjectPath: z.string().min(1),
651
+ token: z.string().uuid(),
652
+ state: z.enum([
653
+ "reserved",
654
+ "ready",
655
+ "removed"
656
+ ]),
657
+ directoryIdentity: z.object({
658
+ dev: z.string().min(1),
659
+ ino: z.string().min(1)
660
+ }).strict().optional(),
661
+ dispatchedAt: z.string().datetime().optional(),
662
+ cleanupStartedAt: z.string().datetime().optional(),
663
+ removedAt: z.string().datetime().optional(),
664
+ cleanupError: z.string().optional()
665
+ }).strict();
666
+ //#endregion
516
667
  //#region src/devtoolsProjectOwnership/windowClose/schema.ts
517
668
  const text = z.string().min(1);
518
669
  const cursorSchema = z.object({
@@ -603,8 +754,10 @@ const recordSchema = z.object({
603
754
  releasedReason: z.enum([
604
755
  "borrowed",
605
756
  "project-closed",
606
- "installation-exited"
757
+ "installation-exited",
758
+ "not-started"
607
759
  ]).optional(),
760
+ snapshot: managedSnapshotSchema.optional(),
608
761
  installationExitRecovery: installationExitRecoverySchema.optional(),
609
762
  error: z.string().optional()
610
763
  }).refine((record) => record.releasedReason === "installation-exited" ? record.state === "released" && record.installationExitRecovery !== void 0 : record.installationExitRecovery === void 0, { message: "Installation exit recovery requires its distinct release reason and forensic evidence." });
@@ -619,7 +772,7 @@ async function readManagedRecord(journalPath, id) {
619
772
  const file = managedRecordPath(journalPath, id);
620
773
  if (!(await fs.lstat(file)).isFile()) throw new Error("Managed DevTools journal record is not a regular file.");
621
774
  const record = recordSchema.parse(JSON.parse(await fs.readFile(file, "utf8")));
622
- if (record.id !== id || record.journalPath !== journalPath || !path.isAbsolute(record.projectPath)) throw new Error("Managed DevTools journal identity does not match its task directory.");
775
+ if (record.id !== id || record.journalPath !== journalPath || !path.isAbsolute(record.projectPath) || record.snapshot && (record.projectPath !== path.join(journalPath, "snapshots", id) || !path.isAbsolute(record.snapshot.sourceProjectPath) || record.snapshot.sourceProjectPath === record.projectPath)) throw new Error("Managed DevTools journal identity does not match its task directory.");
623
776
  return record;
624
777
  }
625
778
  async function writeManagedRecord(record) {
@@ -738,6 +891,110 @@ async function withManagedJournalLock(directory, run) {
738
891
  });
739
892
  }
740
893
  //#endregion
894
+ //#region src/devtoolsProjectOwnership/snapshot/index.ts
895
+ const MARKER = ".weapp-ide-snapshot-owner";
896
+ function managedSnapshotPath(journalPath, id) {
897
+ return path.join(journalPath, "snapshots", id);
898
+ }
899
+ /** 窗口已经释放不代表本任务快照也已回收。 */
900
+ function isManagedWechatProjectFullyReleased(record) {
901
+ return record.state === "released" && (!record.snapshot || record.snapshot.state === "removed");
902
+ }
903
+ function markerValue(record) {
904
+ return JSON.stringify({
905
+ protocol: record.snapshot.protocol,
906
+ journalPath: record.journalPath,
907
+ id: record.id,
908
+ generation: record.generation,
909
+ ownerToken: record.ownerToken,
910
+ token: record.snapshot.token,
911
+ sourceProjectPath: record.snapshot.sourceProjectPath,
912
+ projectPath: record.projectPath,
913
+ target: record.target
914
+ });
915
+ }
916
+ async function directoryStat(directory) {
917
+ const stat = await fs.lstat(directory, { bigint: true });
918
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("Managed DevTools snapshot directory identity changed; refusing filesystem cleanup.");
919
+ return {
920
+ dev: String(stat.dev),
921
+ ino: String(stat.ino)
922
+ };
923
+ }
924
+ function assertPath(record) {
925
+ if (!record.snapshot || record.projectPath !== managedSnapshotPath(record.journalPath, record.id) || !path.isAbsolute(record.snapshot.sourceProjectPath) || record.snapshot.sourceProjectPath === record.projectPath) throw new Error("Managed DevTools snapshot is not bound to its exact task journal.");
926
+ }
927
+ async function verifySnapshot(record, allowIncompleteCreation = false) {
928
+ assertPath(record);
929
+ const snapshot = record.snapshot;
930
+ await directoryStat(record.journalPath);
931
+ await directoryStat(path.dirname(record.projectPath));
932
+ const actual = await directoryStat(record.projectPath);
933
+ if (await fs.realpath(record.projectPath) !== path.join(await fs.realpath(record.journalPath), "snapshots", record.id)) throw new Error("Managed DevTools snapshot escaped its canonical task journal.");
934
+ if (!snapshot.directoryIdentity || actual.dev !== snapshot.directoryIdentity.dev || actual.ino !== snapshot.directoryIdentity.ino) throw new Error("Managed DevTools snapshot directory was replaced; refusing cleanup.");
935
+ const marker = path.join(record.projectPath, MARKER);
936
+ try {
937
+ const stat = await fs.lstat(marker);
938
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || await fs.readFile(marker, "utf8") !== markerValue(record)) throw new Error("Managed DevTools snapshot creation marker changed; refusing cleanup.");
939
+ } catch (error) {
940
+ if (error.code !== "ENOENT" || !snapshot.cleanupStartedAt && !(allowIncompleteCreation && snapshot.state === "reserved" && !snapshot.dispatchedAt)) throw error;
941
+ }
942
+ }
943
+ /** 调用方持有 journal 根锁;先登记预算,再独占创建本次 generation 的快照。 */
944
+ async function prepareManagedProjectSnapshot(record, populate) {
945
+ assertPath(record);
946
+ const snapshot = record.snapshot;
947
+ if (record.state !== "starting" || snapshot.state !== "reserved" || snapshot.directoryIdentity || snapshot.dispatchedAt) throw new Error("Managed DevTools snapshot preparation requires its unused project reservation.");
948
+ await directoryStat(record.journalPath);
949
+ const parent = path.dirname(record.projectPath);
950
+ await fs.mkdir(parent, { mode: 448 }).catch((error) => {
951
+ if (error.code !== "EEXIST") throw error;
952
+ });
953
+ await directoryStat(parent);
954
+ await fs.mkdir(record.projectPath, { mode: 448 });
955
+ snapshot.directoryIdentity = await directoryStat(record.projectPath);
956
+ await writeManagedRecord(record);
957
+ await fs.writeFile(path.join(record.projectPath, MARKER), markerValue(record), {
958
+ encoding: "utf8",
959
+ mode: 384,
960
+ flag: "wx"
961
+ });
962
+ await populate(record.projectPath);
963
+ await verifySnapshot(record);
964
+ snapshot.state = "ready";
965
+ await writeManagedRecord(record);
966
+ }
967
+ /** 窗口与端口证据由原有 owner 协议提供;这里仅回收匹配 provenance 的快照。 */
968
+ async function finalizeManagedProjectSnapshot(record) {
969
+ if (!record.snapshot || record.snapshot.state === "removed") return;
970
+ const snapshot = record.snapshot;
971
+ try {
972
+ assertPath(record);
973
+ if (record.state !== "released" || ![
974
+ "project-closed",
975
+ "installation-exited",
976
+ "not-started"
977
+ ].includes(record.releasedReason ?? "") || record.releasedReason === "not-started" && (snapshot.dispatchedAt || record.openedProjectWindow !== void 0)) throw new Error("Managed DevTools snapshot is still used by an unresolved project; preserving its files.");
978
+ if (await fs.lstat(record.projectPath).then(() => true).catch((error) => {
979
+ if (error.code === "ENOENT") return false;
980
+ throw error;
981
+ })) {
982
+ await verifySnapshot(record, true);
983
+ snapshot.cleanupStartedAt ??= (/* @__PURE__ */ new Date()).toISOString();
984
+ await writeManagedRecord(record);
985
+ await fs.rm(record.projectPath, { recursive: true });
986
+ }
987
+ snapshot.state = "removed";
988
+ snapshot.removedAt = (/* @__PURE__ */ new Date()).toISOString();
989
+ delete snapshot.cleanupError;
990
+ await writeManagedRecord(record);
991
+ } catch (error) {
992
+ snapshot.cleanupError = error instanceof Error ? error.message : String(error);
993
+ await writeManagedRecord(record);
994
+ throw error;
995
+ }
996
+ }
997
+ //#endregion
741
998
  //#region src/devtoolsProjectOwnership/windowBudget.ts
742
999
  function maximumOwnedWindows(options) {
743
1000
  const selected = options.maxOwnedWindows ?? process.env["WEAPP_IDE_MANAGED_PROJECT_MAX_WINDOWS"] ?? 1;
@@ -751,7 +1008,7 @@ function sameTarget(first, second) {
751
1008
  function checkManagedWindowBudget(records, options) {
752
1009
  const maximum = maximumOwnedWindows(options);
753
1010
  const projectPath = path.resolve(options.projectPath);
754
- if (records.some((record) => record.state !== "released" && record.projectPath === projectPath && sameTarget(record.target, options.target))) throw new Error("Managed DevTools project already has an active session; use its existing endpoint in connect-only mode instead of starting it again.");
1011
+ if (records.some((record) => !isManagedWechatProjectFullyReleased(record) && (record.projectPath === projectPath || record.snapshot?.sourceProjectPath === projectPath) && sameTarget(record.target, options.target))) throw new Error("Managed DevTools project already has an active session; use its existing endpoint in connect-only mode instead of starting it again.");
755
1012
  const owned = records.filter((record) => record.state === "owned").length;
756
1013
  if (owned >= maximum) throw new Error(`Managed DevTools window budget exhausted (${owned}/${maximum}); close the previous owned project before another start.`);
757
1014
  }
@@ -1331,28 +1588,28 @@ const options = {
1331
1588
  reject: false,
1332
1589
  env: { LC_ALL: "C" }
1333
1590
  };
1334
- const pathSchema = z.array(z.object({
1335
- pid: z.number().int().positive(),
1336
- executable: z.string().nullable()
1337
- }).strict());
1338
- const kernelPathsScript = `
1339
- ObjC.import('Foundation');
1340
- ObjC.bindFunction('malloc', ['void *', ['unsigned long']]);
1341
- ObjC.bindFunction('free', ['void', ['void *']]);
1342
- ObjC.bindFunction('proc_pidpath', ['int', ['int', 'void *', 'uint32_t']]);
1343
- ObjC.bindFunction('strchr', ['char *', ['void *', 'int']]);
1344
- function run(argv) {
1345
- var pids = JSON.parse(argv[0]);
1346
- var buffer = $.malloc(4096);
1347
- if (!buffer) throw new Error('Cannot allocate process path buffer.');
1348
- try {
1349
- return JSON.stringify(pids.map(function(pid) {
1350
- var count = $.proc_pidpath(pid, buffer, 4096);
1351
- return { pid: pid, executable: count > 0 ? $.strchr(buffer, 47) : null };
1352
- }));
1353
- } finally { $.free(buffer); }
1591
+ const textImageDecoder = new TextDecoder("utf-8", { fatal: true });
1592
+ /** 按本机 LC_ALL=C 的 lsof 表示还原字节;有歧义的控制表示不能用于负归属判断。 */
1593
+ function decodeTextImagePath(wire) {
1594
+ if (!wire.startsWith("/") || /[^\u0020-\u007E]|\^[A-GKN-Z[\]\\^_]/.test(wire)) throw new Error("Installation-exit recovery received an unverifiable executable image path.");
1595
+ if (!wire.includes("\\")) return wire;
1596
+ const bytes = Buffer.allocUnsafe(wire.length);
1597
+ let length = 0;
1598
+ for (let index = 0; index < wire.length; index++) {
1599
+ let byte = wire.charCodeAt(index);
1600
+ if (wire[index] === "\\") {
1601
+ const escape = wire[++index];
1602
+ if (escape === "\\") byte = 92;
1603
+ else if (escape === "x" && /^[\da-f]{2}$/.test(wire.slice(index + 1, index + 3))) {
1604
+ byte = Number.parseInt(wire.slice(index + 1, index + 3), 16);
1605
+ index += 2;
1606
+ if (byte < 128) throw new Error("Installation-exit recovery received an unverifiable executable image path.");
1607
+ } else throw new Error("Installation-exit recovery received an unverifiable executable image path.");
1608
+ }
1609
+ bytes[length++] = byte;
1610
+ }
1611
+ return textImageDecoder.decode(bytes.subarray(0, length));
1354
1612
  }
1355
- `;
1356
1613
  /** ps 仅用于 PID、启动时间和僵尸状态,不能用可修改的 comm 代替实际可执行文件。 */
1357
1614
  async function readDarwinProcessStates() {
1358
1615
  const inspection = execa("/bin/ps", ["-axo", "pid=,stat=,lstart="], options);
@@ -1372,28 +1629,9 @@ async function readDarwinProcessStates() {
1372
1629
  if (new Set(records.map((record) => record.pid)).size !== records.length) throw new Error("Installation-exit recovery found duplicate process identities.");
1373
1630
  return records.filter((record) => record.pid !== inspectorPid);
1374
1631
  }
1375
- /** 系统 JXA/libproc 不可用时明确拒绝恢复,不安装工具或以进程标题降级。 */
1376
- async function readDarwinKernelPaths(pids) {
1377
- const result = await execa("/usr/bin/osascript", [
1378
- "-l",
1379
- "JavaScript",
1380
- "-e",
1381
- kernelPathsScript,
1382
- JSON.stringify(pids)
1383
- ], options);
1384
- if (result.exitCode !== 0 || result.stderr?.trim()) throw new Error("Installation-exit recovery cannot inspect kernel executable paths.");
1385
- let parsed;
1386
- try {
1387
- parsed = pathSchema.parse(JSON.parse(result.stdout));
1388
- } catch {
1389
- throw new Error("Installation-exit recovery received incomplete kernel executable paths.");
1390
- }
1391
- const entries = new Map(parsed.map((entry) => [entry.pid, entry.executable]));
1392
- if (entries.size !== pids.length || parsed.length !== pids.length || pids.some((pid) => !entries.has(pid))) throw new Error("Installation-exit recovery received incomplete kernel process identities.");
1393
- return entries;
1394
- }
1395
1632
  /** 已删除可执行文件无法经 proc_pidpath 读取时,核验全部 program-text 映像而非猜测首项。 */
1396
- async function readDarwinTextImages(pid) {
1633
+ async function readDarwinTextImages(pid, timeoutMs = options.timeout) {
1634
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error("Installation-exit recovery cannot inspect executable text images without a positive timeout.");
1397
1635
  const result = await execa("/usr/sbin/lsof", [
1398
1636
  "-nP",
1399
1637
  "-a",
@@ -1402,29 +1640,25 @@ async function readDarwinTextImages(pid) {
1402
1640
  "-d",
1403
1641
  "txt",
1404
1642
  "-F0pftn"
1405
- ], options);
1406
- if (result.exitCode !== 0 || result.stderr?.trim()) throw new Error("Installation-exit recovery cannot inspect all executable text images.");
1407
- const fields = result.stdout.split("\0").map((field) => field.replace(/^\r?\n/, "")).filter(Boolean);
1408
- if (fields.shift() !== `p${pid}` || fields.length === 0 || fields.length % 3 !== 0) throw new Error("Installation-exit recovery received incomplete executable text images.");
1643
+ ], {
1644
+ ...options,
1645
+ timeout: Math.min(options.timeout, timeoutMs)
1646
+ });
1647
+ if (result.exitCode !== 0 || result.timedOut || result.signal || result.stderr?.trim()) throw new Error(`Installation-exit recovery cannot inspect all executable text images for PID ${pid}.`, { cause: {
1648
+ exitCode: result.exitCode,
1649
+ stderr: result.stderr?.trim()
1650
+ } });
1651
+ const fields = result.stdout.split("\0").map((field) => field.replace(/^\r?\n/, ""));
1652
+ if (fields.pop() !== "" || fields.shift() !== `p${pid}` || fields.length === 0 || fields.length % 3 !== 0) throw new Error("Installation-exit recovery received incomplete executable text images.");
1409
1653
  const paths = [];
1410
1654
  for (let index = 0; index < fields.length; index += 3) {
1411
1655
  if (fields[index] !== "ftxt" || fields[index + 1] !== "tREG" || !fields[index + 2]?.startsWith("n")) throw new Error("Installation-exit recovery received unverifiable executable text images.");
1412
- paths.push(fields[index + 2].slice(1));
1656
+ paths.push(decodeTextImagePath(fields[index + 2].slice(1)));
1413
1657
  }
1414
1658
  return paths;
1415
1659
  }
1416
1660
  //#endregion
1417
1661
  //#region src/devtoolsProjectOwnership/installationExit/processes.ts
1418
- /** 已删除映像仍有内核 vnode 路径;从现存祖先解析符号链接,不把缺失文件当成已退出进程。 */
1419
- async function canonicalImagePath(file) {
1420
- if (!path.isAbsolute(file) || /[\r\n\0]/.test(file)) throw new Error("Installation-exit recovery received an unverifiable executable image path.");
1421
- try {
1422
- return await fs.realpath(file);
1423
- } catch (error) {
1424
- if (error.code !== "ENOENT" || path.dirname(file) === file) throw new Error("Installation-exit recovery cannot resolve an executable image path.", { cause: error });
1425
- return path.join(await canonicalImagePath(path.dirname(file)), path.basename(file));
1426
- }
1427
- }
1428
1662
  /** 仅支持已验证的 macOS 全安装清单;未知平台、权限错误或不完整映像均拒绝推断退出。 */
1429
1663
  async function inspectExitedWechatInstallation(target, platform = process.platform) {
1430
1664
  if (platform !== "darwin") throw new Error("Installation-exit recovery requires the verified macOS process inventory.");
@@ -1453,11 +1687,24 @@ async function inspectExitedWechatInstallation(target, platform = process.platfo
1453
1687
  continue;
1454
1688
  }
1455
1689
  const executable = kernelPaths.get(entry.pid);
1456
- const images = executable ? [executable] : await readDarwinTextImages(entry.pid);
1690
+ let images;
1691
+ try {
1692
+ images = executable ? [executable] : await readDarwinTextImages(entry.pid);
1693
+ } catch (error) {
1694
+ const latest = await readDarwinProcessStates().then((states) => states.find((process) => process.pid === entry.pid), (confirmationError) => {
1695
+ throw new AggregateError([error, confirmationError], "Installation-exit recovery cannot independently confirm process exit after failed image inspection.");
1696
+ });
1697
+ if (!latest) {
1698
+ exitedProcessCount++;
1699
+ continue;
1700
+ }
1701
+ if (latest.started !== entry.started) throw new Error("Installation-exit recovery process identity changed during inspection.", { cause: error });
1702
+ throw error;
1703
+ }
1457
1704
  if (!images.length) throw new Error("Installation-exit recovery received no verifiable executable text images.");
1458
1705
  for (const image of images) {
1459
- const resolved = await canonicalImagePath(image);
1460
- const relative = path.relative(installationRoot, resolved);
1706
+ if (!path.isAbsolute(image) || /[\r\n\0]/.test(image)) throw new Error("Installation-exit recovery received an unverifiable executable image path.");
1707
+ const relative = path.relative(installationRoot, image);
1461
1708
  if (!relative || !path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`)) throw new Error("The selected WeChat DevTools installation still has a live process; no ownership was recovered.");
1462
1709
  }
1463
1710
  if (executable) kernelPathProcessCount++;
@@ -1497,7 +1744,7 @@ async function fingerprint(record) {
1497
1744
  return createHash("sha256").update(await fs.readFile(file)).digest("hex");
1498
1745
  }
1499
1746
  function canRecoverAfterExit(record) {
1500
- return record.state === "starting" || record.state === "unconfirmed" || record.state === "failed" && record.openedProjectWindow === true && record.host !== void 0 && record.port !== void 0 && record.error !== void 0;
1747
+ return record.state === "starting" || record.state === "unconfirmed" || record.state === "borrowed" && record.snapshot !== void 0 && record.openedProjectWindow === false || record.state === "failed" && record.openedProjectWindow === true && record.host !== void 0 && record.port !== void 0 && record.error !== void 0;
1501
1748
  }
1502
1749
  async function recoverAfterExit(options) {
1503
1750
  const { target, recoveryScope } = options;
@@ -1512,9 +1759,12 @@ async function recoverAfterExit(options) {
1512
1759
  if (new Set(records.map((record) => record.id)).size !== records.length) throw new Error("Installation-exit recovery found duplicate record identities.");
1513
1760
  if (records.some((record) => !isDeepStrictEqual(record.target, target))) throw new Error("Installation-exit recovery cannot include another installation or profile.");
1514
1761
  if (records.some((record) => record.state !== "released" && !canRecoverAfterExit(record))) throw new Error("Installation-exit recovery refuses confirmed or unresolved project-close ownership.");
1515
- const candidates = (await readManagedWechatProjectRecords(journalPath)).filter(canRecoverAfterExit);
1762
+ const scopeRecords = await readManagedWechatProjectRecords(journalPath);
1763
+ const candidates = scopeRecords.filter(canRecoverAfterExit);
1764
+ const pendingSnapshots = scopeRecords.filter((record) => record.state === "released" && !isManagedWechatProjectFullyReleased(record));
1516
1765
  if (!candidates.length) {
1517
1766
  await assertMachineE2ELeaseRecoveryScope(recoveryScope);
1767
+ for (const record of pendingSnapshots) await finalizeManagedProjectSnapshot(record);
1518
1768
  return { recoveredRecordIds: [] };
1519
1769
  }
1520
1770
  const before = new Map(await Promise.all(records.map(async (record) => [record.id, await fingerprint(record)])));
@@ -1544,7 +1794,7 @@ async function recoverAfterExit(options) {
1544
1794
  await assertMachineE2ELeaseRecoveryScope(recoveryScope);
1545
1795
  if (await fingerprint(record) !== before.get(record.id)) throw new Error("Installation-exit recovery journal bytes changed before recording recovery.");
1546
1796
  const previousState = record.state;
1547
- if (previousState !== "starting" && previousState !== "unconfirmed" && previousState !== "failed") throw new Error("Installation-exit recovery cannot replace a confirmed ownership state.");
1797
+ if (previousState !== "starting" && previousState !== "unconfirmed" && previousState !== "failed" && previousState !== "borrowed") throw new Error("Installation-exit recovery cannot replace a confirmed ownership state.");
1548
1798
  record.installationExitRecovery = {
1549
1799
  protocol: "wechat-devtools-installation-exit-v1",
1550
1800
  recoveredAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1566,8 +1816,10 @@ async function recoverAfterExit(options) {
1566
1816
  record.state = "released";
1567
1817
  record.releasedReason = "installation-exited";
1568
1818
  await writeManagedRecord(record);
1819
+ await finalizeManagedProjectSnapshot(record);
1569
1820
  recoveredRecordIds.push(record.id);
1570
1821
  }
1822
+ for (const record of pendingSnapshots) await finalizeManagedProjectSnapshot(record);
1571
1823
  return { recoveredRecordIds };
1572
1824
  });
1573
1825
  }
@@ -1587,7 +1839,7 @@ async function release(record, reason) {
1587
1839
  delete record.error;
1588
1840
  await writeManagedRecord(record);
1589
1841
  }
1590
- async function closeRecord(record) {
1842
+ async function closeWindowRecord(record) {
1591
1843
  if (record.state === "released") return;
1592
1844
  if (record.state === "borrowed" && record.openedProjectWindow === false) {
1593
1845
  await release(record, "borrowed");
@@ -1631,6 +1883,17 @@ async function closeRecord(record) {
1631
1883
  throw error;
1632
1884
  }
1633
1885
  }
1886
+ async function closeRecord(record) {
1887
+ if (isManagedWechatProjectFullyReleased(record)) return;
1888
+ if (record.state !== "released") {
1889
+ if (record.snapshot && !record.snapshot.dispatchedAt && record.openedProjectWindow === void 0) await release(record, "not-started");
1890
+ else {
1891
+ if (record.snapshot && record.openedProjectWindow === false) throw new Error("Managed DevTools snapshot received a borrowed window receipt; preserving files until installation exit is proven.");
1892
+ await closeWindowRecord(record);
1893
+ }
1894
+ }
1895
+ await finalizeManagedProjectSnapshot(record);
1896
+ }
1634
1897
  /** 窗口销毁与连接断开分别验证;失败保留登记供后续继续观察。 */
1635
1898
  function closeManagedWechatProject(options) {
1636
1899
  const journalPath = resolveManagedJournal(options.journalPath);
@@ -1648,6 +1911,7 @@ async function beginManagedWechatProject(options) {
1648
1911
  const journalPath = resolveManagedJournal(options.journalPath);
1649
1912
  if (!journalPath) return;
1650
1913
  const id = randomUUID();
1914
+ const projectPath = options.snapshotProject ? managedSnapshotPath(journalPath, id) : path.resolve(options.projectPath);
1651
1915
  await withManagedJournalLock(journalPath, async (scopeRoot) => {
1652
1916
  const previous = await readManagedWechatProjectRecords(scopeRoot);
1653
1917
  if (previous.some((record) => [
@@ -1655,7 +1919,7 @@ async function beginManagedWechatProject(options) {
1655
1919
  "unconfirmed",
1656
1920
  "closing",
1657
1921
  "failed"
1658
- ].includes(record.state))) throw new Error("Managed DevTools journal contains unresolved ownership or cleanup; refusing another project start.");
1922
+ ].includes(record.state) || record.state === "released" && !isManagedWechatProjectFullyReleased(record) || record.state === "borrowed" && record.snapshot?.state !== void 0 && record.snapshot.state !== "removed")) throw new Error("Managed DevTools journal contains unresolved ownership or cleanup; refusing another project start.");
1659
1923
  checkManagedWindowBudget(previous, options);
1660
1924
  const now = (/* @__PURE__ */ new Date()).toISOString();
1661
1925
  await writeManagedRecord({
@@ -1666,7 +1930,13 @@ async function beginManagedWechatProject(options) {
1666
1930
  ownerToken: managedProcessToken,
1667
1931
  ownerPid: process.pid,
1668
1932
  target: { ...options.target },
1669
- projectPath: path.resolve(options.projectPath),
1933
+ projectPath,
1934
+ ...options.snapshotProject ? { snapshot: {
1935
+ protocol: "wechat-devtools-project-snapshot-v1",
1936
+ sourceProjectPath: path.resolve(options.projectPath),
1937
+ token: randomUUID(),
1938
+ state: "reserved"
1939
+ } } : {},
1670
1940
  state: "starting",
1671
1941
  port: options.port,
1672
1942
  createdAt: now,
@@ -1676,10 +1946,36 @@ async function beginManagedWechatProject(options) {
1676
1946
  return {
1677
1947
  id,
1678
1948
  journalPath,
1949
+ projectPath,
1950
+ prepareSnapshot: async (populate) => {
1951
+ await withManagedJournalLock(journalPath, async () => {
1952
+ const record = await readManagedRecord(journalPath, id);
1953
+ try {
1954
+ await prepareManagedProjectSnapshot(record, populate);
1955
+ } catch (error) {
1956
+ if (record.snapshot) {
1957
+ record.snapshot.cleanupError = describe(error);
1958
+ await writeManagedRecord(record);
1959
+ }
1960
+ throw error;
1961
+ }
1962
+ });
1963
+ },
1964
+ markLaunchDispatched: async () => {
1965
+ await withManagedJournalLock(journalPath, async () => {
1966
+ const record = await readManagedRecord(journalPath, id);
1967
+ if (record.snapshot) {
1968
+ if (record.state !== "starting" || record.snapshot.state !== "ready" || record.snapshot.dispatchedAt) throw new Error("Managed DevTools snapshot is not ready for its first start dispatch.");
1969
+ record.snapshot.dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
1970
+ await writeManagedRecord(record);
1971
+ }
1972
+ });
1973
+ },
1679
1974
  confirm: async (result) => {
1680
1975
  await withManagedJournalLock(journalPath, async () => {
1681
1976
  const record = await readManagedRecord(journalPath, id);
1682
1977
  if (record.state !== "starting" && record.state !== "unconfirmed") throw new Error("Managed DevTools start receipt was already recorded.");
1978
+ if (record.snapshot && (!record.snapshot.dispatchedAt || record.snapshot.state !== "ready")) throw new Error("Managed DevTools snapshot start receipt has no prepared dispatch intent.");
1683
1979
  if (typeof result.openedProjectWindow !== "boolean" || !Number.isInteger(result.port) || result.port <= 0 || result.port > 65535 || record.port !== void 0 && record.port !== result.port) throw new Error("Managed DevTools start receipt does not match its requested port.");
1684
1980
  record.openedProjectWindow = result.openedProjectWindow;
1685
1981
  record.port = result.port;
@@ -1723,4 +2019,4 @@ async function cleanupManagedWechatProjects(options = {}) {
1723
2019
  if (errors.length) throw new AggregateError(errors, "Managed DevTools project cleanup remains incomplete; stop the acceptance lane.");
1724
2020
  }
1725
2021
  //#endregion
1726
- export { readManagedRecord as a, withManagedJournalLock as c, MANAGED_PROJECT_MAX_WINDOWS_ENV as d, assertManagedInstallation as f, startWechatIdeAgent as h, recoverManagedWechatProjectsAfterInstallationExit as i, writeManagedRecord as l, sameManagedProcess as m, cleanupManagedWechatProjects as n, readManagedWechatProjectRecords as o, inspectManagedProjectHost as p, closeManagedWechatProject as r, resolveManagedJournal as s, beginManagedWechatProject as t, MANAGED_PROJECT_JOURNAL_ENV as u };
2022
+ export { isManagedWechatProjectFullyReleased as a, resolveManagedJournal as c, assertManagedInstallation as d, inspectManagedProjectHost as f, recoverManagedWechatProjectsAfterInstallationExit as i, withManagedJournalLock as l, startWechatIdeAgent as m, cleanupManagedWechatProjects as n, readManagedRecord as o, sameManagedProcess as p, closeManagedWechatProject as r, readManagedWechatProjectRecords as s, beginManagedWechatProject as t, writeManagedRecord as u };