@lemoncat7/dsh-partner 1.16.1 → 2.0.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.
Files changed (157) hide show
  1. package/README.md +23 -0
  2. package/cordis.patch.yml +1 -0
  3. package/docs/channel-reply-progress.md +18 -0
  4. package/docs/releases/1.16.2.md +11 -0
  5. package/docs/releases/2.0.0.md +34 -0
  6. package/docs/storage-migration.md +72 -0
  7. package/lib/agent-runtime.d.ts +2 -1
  8. package/lib/agent-runtime.d.ts.map +1 -1
  9. package/lib/agent-runtime.js +23 -8
  10. package/lib/agent-runtime.js.map +1 -1
  11. package/lib/api.d.ts +2 -0
  12. package/lib/api.d.ts.map +1 -1
  13. package/lib/api.js +49 -1
  14. package/lib/api.js.map +1 -1
  15. package/lib/attachments/service.d.ts +10 -0
  16. package/lib/attachments/service.d.ts.map +1 -1
  17. package/lib/attachments/service.js +68 -9
  18. package/lib/attachments/service.js.map +1 -1
  19. package/lib/channels/direct/progress-transport.d.ts +15 -0
  20. package/lib/channels/direct/progress-transport.d.ts.map +1 -0
  21. package/lib/channels/direct/progress-transport.js +51 -0
  22. package/lib/channels/direct/progress-transport.js.map +1 -0
  23. package/lib/channels/direct/transport.d.ts +2 -0
  24. package/lib/channels/direct/transport.d.ts.map +1 -1
  25. package/lib/channels/direct/transport.js +6 -0
  26. package/lib/channels/direct/transport.js.map +1 -1
  27. package/lib/channels/manager.d.ts.map +1 -1
  28. package/lib/channels/manager.js +23 -6
  29. package/lib/channels/manager.js.map +1 -1
  30. package/lib/channels/reply-progress-controller.d.ts +32 -0
  31. package/lib/channels/reply-progress-controller.d.ts.map +1 -0
  32. package/lib/channels/reply-progress-controller.js +116 -0
  33. package/lib/channels/reply-progress-controller.js.map +1 -0
  34. package/lib/client-controller.d.ts +1 -1
  35. package/lib/client-controller.d.ts.map +1 -1
  36. package/lib/client.d.ts.map +1 -1
  37. package/lib/client.js +8657 -1564
  38. package/lib/client.js.map +4 -4
  39. package/lib/companions/removal.d.ts.map +1 -1
  40. package/lib/companions/removal.js +3 -0
  41. package/lib/companions/removal.js.map +1 -1
  42. package/lib/concern-store.d.ts +5 -1
  43. package/lib/concern-store.d.ts.map +1 -1
  44. package/lib/concern-store.js +13 -4
  45. package/lib/concern-store.js.map +1 -1
  46. package/lib/config.d.ts +1 -0
  47. package/lib/config.d.ts.map +1 -1
  48. package/lib/config.js +6 -0
  49. package/lib/config.js.map +1 -1
  50. package/lib/execution/reply-progress.d.ts +7 -0
  51. package/lib/execution/reply-progress.d.ts.map +1 -0
  52. package/lib/execution/reply-progress.js +15 -0
  53. package/lib/execution/reply-progress.js.map +1 -0
  54. package/lib/index.d.ts.map +1 -1
  55. package/lib/index.js +162 -132
  56. package/lib/index.js.map +1 -1
  57. package/lib/memory-store.d.ts +4 -1
  58. package/lib/memory-store.d.ts.map +1 -1
  59. package/lib/memory-store.js +10 -2
  60. package/lib/memory-store.js.map +1 -1
  61. package/lib/notifications/domain.d.ts +2 -1
  62. package/lib/notifications/domain.d.ts.map +1 -1
  63. package/lib/notifications/store.d.ts +8 -0
  64. package/lib/notifications/store.d.ts.map +1 -1
  65. package/lib/notifications/store.js +81 -17
  66. package/lib/notifications/store.js.map +1 -1
  67. package/lib/pendant/markdown.d.ts +6 -0
  68. package/lib/pendant/markdown.d.ts.map +1 -0
  69. package/lib/pendant/markdown.js +92 -0
  70. package/lib/pendant/markdown.js.map +1 -0
  71. package/lib/pendant/reader.d.ts.map +1 -1
  72. package/lib/pendant/reader.js +2 -1
  73. package/lib/pendant/reader.js.map +1 -1
  74. package/lib/pendant/widget.d.ts.map +1 -1
  75. package/lib/pendant/widget.js +5 -1
  76. package/lib/pendant/widget.js.map +1 -1
  77. package/lib/storage/atomic.d.ts +3 -0
  78. package/lib/storage/atomic.d.ts.map +1 -0
  79. package/lib/storage/atomic.js +35 -0
  80. package/lib/storage/atomic.js.map +1 -0
  81. package/lib/storage/bootstrap.d.ts +8 -0
  82. package/lib/storage/bootstrap.d.ts.map +1 -0
  83. package/lib/storage/bootstrap.js +31 -0
  84. package/lib/storage/bootstrap.js.map +1 -0
  85. package/lib/storage/coordinator.d.ts +36 -0
  86. package/lib/storage/coordinator.d.ts.map +1 -0
  87. package/lib/storage/coordinator.js +103 -0
  88. package/lib/storage/coordinator.js.map +1 -0
  89. package/lib/storage/layout.d.ts +13 -0
  90. package/lib/storage/layout.d.ts.map +1 -0
  91. package/lib/storage/layout.js +30 -0
  92. package/lib/storage/layout.js.map +1 -0
  93. package/lib/storage/lease.d.ts +5 -0
  94. package/lib/storage/lease.d.ts.map +1 -0
  95. package/lib/storage/lease.js +25 -0
  96. package/lib/storage/lease.js.map +1 -0
  97. package/lib/storage/legacy-backups.d.ts +3 -0
  98. package/lib/storage/legacy-backups.d.ts.map +1 -0
  99. package/lib/storage/legacy-backups.js +14 -0
  100. package/lib/storage/legacy-backups.js.map +1 -0
  101. package/lib/storage/migrate-layout.d.ts +18 -0
  102. package/lib/storage/migrate-layout.d.ts.map +1 -0
  103. package/lib/storage/migrate-layout.js +204 -0
  104. package/lib/storage/migrate-layout.js.map +1 -0
  105. package/lib/storage/migrations/index.d.ts +3 -0
  106. package/lib/storage/migrations/index.d.ts.map +1 -0
  107. package/lib/storage/migrations/index.js +4 -0
  108. package/lib/storage/migrations/index.js.map +1 -0
  109. package/lib/storage/migrations/registry.d.ts +19 -0
  110. package/lib/storage/migrations/registry.d.ts.map +1 -0
  111. package/lib/storage/migrations/registry.js +40 -0
  112. package/lib/storage/migrations/registry.js.map +1 -0
  113. package/lib/storage/migrations/v0-to-v1.d.ts +5 -0
  114. package/lib/storage/migrations/v0-to-v1.d.ts.map +1 -0
  115. package/lib/storage/migrations/v0-to-v1.js +11 -0
  116. package/lib/storage/migrations/v0-to-v1.js.map +1 -0
  117. package/lib/storage/notice.d.ts +4 -0
  118. package/lib/storage/notice.d.ts.map +1 -0
  119. package/lib/storage/notice.js +13 -0
  120. package/lib/storage/notice.js.map +1 -0
  121. package/lib/storage/preflight.d.ts +40 -0
  122. package/lib/storage/preflight.d.ts.map +1 -0
  123. package/lib/storage/preflight.js +208 -0
  124. package/lib/storage/preflight.js.map +1 -0
  125. package/lib/storage/safe-files.d.ts +6 -0
  126. package/lib/storage/safe-files.d.ts.map +1 -0
  127. package/lib/storage/safe-files.js +125 -0
  128. package/lib/storage/safe-files.js.map +1 -0
  129. package/lib/storage/source-archive.d.ts +14 -0
  130. package/lib/storage/source-archive.d.ts.map +1 -0
  131. package/lib/storage/source-archive.js +98 -0
  132. package/lib/storage/source-archive.js.map +1 -0
  133. package/lib/storage/split-state.d.ts +14 -0
  134. package/lib/storage/split-state.d.ts.map +1 -0
  135. package/lib/storage/split-state.js +107 -0
  136. package/lib/storage/split-state.js.map +1 -0
  137. package/lib/store.d.ts +5 -0
  138. package/lib/store.d.ts.map +1 -1
  139. package/lib/store.js +12 -1
  140. package/lib/store.js.map +1 -1
  141. package/lib/ui/storage-recovery-marker.d.ts +4 -0
  142. package/lib/ui/storage-recovery-marker.d.ts.map +1 -0
  143. package/lib/ui/storage-recovery-marker.js +19 -0
  144. package/lib/ui/storage-recovery-marker.js.map +1 -0
  145. package/lib/ui/storage-recovery.d.ts +20 -0
  146. package/lib/ui/storage-recovery.d.ts.map +1 -0
  147. package/lib/ui/storage-recovery.js +39 -0
  148. package/lib/ui/storage-recovery.js.map +1 -0
  149. package/lib/ui/storage-settings.d.ts +2 -0
  150. package/lib/ui/storage-settings.d.ts.map +1 -0
  151. package/lib/ui/storage-settings.js +90 -0
  152. package/lib/ui/storage-settings.js.map +1 -0
  153. package/lib/ui/workspace-navigation.d.ts +78 -0
  154. package/lib/ui/workspace-navigation.d.ts.map +1 -0
  155. package/lib/ui/workspace-navigation.js +28 -0
  156. package/lib/ui/workspace-navigation.js.map +1 -0
  157. package/package.json +5 -1
@@ -0,0 +1,204 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, readdir, rename, rm, rmdir } from 'node:fs/promises';
3
+ import { dirname, join, relative, resolve, sep, isAbsolute } from 'node:path';
4
+ import { DatabaseSync } from 'node:sqlite';
5
+ import { PartnerInboxStore } from '../notifications/store.js';
6
+ import { AttachmentDeliveryService } from '../attachments/service.js';
7
+ import { storageLayout } from './layout.js';
8
+ import { atomicJson, syncDirectory } from './atomic.js';
9
+ import { copyVerified, exists, safeTree, verifyDatabases } from './safe-files.js';
10
+ import { SplitStatePersistence, privateStateOwners } from './split-state.js';
11
+ import { storageConfigPath, readStorageConfig } from './bootstrap.js';
12
+ import { legacyMemoryBackups } from './legacy-backups.js';
13
+ import { prepareSourceArchive, cleanupArchivedSources } from './source-archive.js';
14
+ /** Called only after every legacy writer has been stopped and drained. */
15
+ export async function migrateLayout(statePath, root, state) {
16
+ statePath = resolve(statePath);
17
+ root = resolve(root);
18
+ const layout = storageLayout(statePath, root), journalPath = join(layout.legacyPublic, 'storage-migration.json');
19
+ const current = await readStorageConfig(statePath, root);
20
+ if (current.storageVersion === 1)
21
+ return { storageVersion: 1, backupPath: join(layout.legacyPublic, 'migration-backups', current.transactionId), retainedLegacy: true };
22
+ const oldJournal = await exists(journalPath) ? JSON.parse(await readFile(journalPath, 'utf8')) : undefined;
23
+ if (oldJournal) {
24
+ if (!/^[a-f0-9-]{36}$/.test(oldJournal.id) || !Array.isArray(oldJournal.owners))
25
+ throw Error('迁移恢复清单无效');
26
+ // Only remove this transaction's uncommitted generated targets, never old data.
27
+ for (const target of [layout.publicRoot, ...oldJournal.owners.map(id => layout.privateRoot(id))]) {
28
+ for (const path of [target, target + '.migrate-' + oldJournal.id])
29
+ if (await exists(path)) {
30
+ await safeTree(path);
31
+ if (path !== target && (await readdir(path)).length === 0) {
32
+ await rmdir(path);
33
+ continue;
34
+ }
35
+ const marker = JSON.parse(await readFile(join(path, 'migration-owner.json'), 'utf8'));
36
+ if (marker.transactionId !== oldJournal.id)
37
+ throw Error('迁移目标不是当前事务创建,拒绝覆盖');
38
+ await rm(path, { recursive: true, force: false });
39
+ }
40
+ }
41
+ }
42
+ const id = randomUUID(), owners = new Set(privateStateOwners(state));
43
+ const inboxPath = join(layout.legacyPublic, 'partner-inbox.sqlite');
44
+ let notices = [];
45
+ if (await exists(inboxPath)) {
46
+ await safeTree(inboxPath);
47
+ const db = new DatabaseSync(inboxPath, { readOnly: true });
48
+ try {
49
+ notices = db.prepare('SELECT payload,read_at FROM notices').all().map(row => ({ ...JSON.parse(String(row.payload)), ...(row.read_at === null ? {} : { readAt: Number(row.read_at) }) }));
50
+ }
51
+ finally {
52
+ db.close();
53
+ }
54
+ for (const notice of notices)
55
+ if (notice.kind !== 'system')
56
+ owners.add(notice.companionId);
57
+ if (notices.length > 200)
58
+ throw Error('消息库超出预期保留数量,拒绝截断迁移');
59
+ }
60
+ const deliveryRoot = join(layout.legacyPublic, 'attachment-deliveries'), deliveryDb = join(deliveryRoot, 'deliveries.sqlite');
61
+ let deliveries = [];
62
+ if (await exists(deliveryDb)) {
63
+ await safeTree(deliveryRoot);
64
+ const db = new DatabaseSync(deliveryDb, { readOnly: true });
65
+ try {
66
+ deliveries = db.prepare('SELECT payload FROM deliveries').all().map(row => JSON.parse(String(row.payload)));
67
+ }
68
+ finally {
69
+ db.close();
70
+ }
71
+ for (const item of deliveries)
72
+ owners.add(item.companionId);
73
+ }
74
+ for (const owner of owners)
75
+ layout.privateRoot(owner);
76
+ const targets = [layout.publicRoot, ...[...owners].map(owner => layout.privateRoot(owner))];
77
+ const sources = [layout.legacySkills, deliveryRoot, ...[...owners].flatMap(owner => ['memory', 'concerns', 'memory-backup'].map(name => join(layout.legacyCompanion(owner), name)))];
78
+ for (const owner of owners)
79
+ for (const name of await legacyMemoryBackups(layout.legacyCompanion(owner)))
80
+ sources.push(join(layout.legacyCompanion(owner), name));
81
+ for (const target of targets)
82
+ for (const source of sources)
83
+ if (target === source || target.startsWith(source + sep) || source.startsWith(target + sep))
84
+ throw Error('迁移源与目标目录重叠,拒绝执行');
85
+ for (const target of targets) {
86
+ await safeTree(target);
87
+ if (await exists(target))
88
+ throw Error('目标目录已存在,拒绝覆盖:' + target);
89
+ }
90
+ const journal = { id, phase: 'copying', owners: [...owners] };
91
+ await atomicJson(journalPath, journal);
92
+ const staging = (path) => path + '.migrate-' + id;
93
+ const privateStage = (owner) => staging(layout.privateRoot(owner));
94
+ for (const target of targets) {
95
+ await mkdir(staging(target), { recursive: true, mode: 0o700 });
96
+ await atomicJson(join(staging(target), 'migration-owner.json'), { transactionId: id });
97
+ }
98
+ const backup = join(layout.legacyPublic, 'migration-backups', id);
99
+ await copyVerified(statePath, join(backup, 'state.json'));
100
+ await prepareSourceArchive(statePath, root, id, [...owners]);
101
+ // Original directories remain untouched and serve as the pre-upgrade backup.
102
+ await atomicJson(join(backup, 'sources.json'), { transactionId: id, workspaceRoot: root, retained: targets.slice(1).map((_, i) => layout.legacyCompanion(journal.owners[i])), publicSource: layout.legacyPublic });
103
+ for (const owner of owners) {
104
+ const source = layout.legacyCompanion(owner), target = privateStage(owner);
105
+ await copyVerified(join(source, 'memory'), join(target, 'memory'));
106
+ await copyVerified(join(source, 'concerns'), join(target, 'concerns'));
107
+ // Preserve legacy backups opaquely; never merge their contents into live memory.
108
+ for (const name of await legacyMemoryBackups(source))
109
+ await copyVerified(join(source, name), join(target, 'backups', 'legacy-memory', name));
110
+ }
111
+ await copyVerified(layout.legacySkills, join(staging(layout.publicRoot), 'skills'));
112
+ const next = structuredClone(state);
113
+ next.notificationDeliveries ??= [];
114
+ for (const skill of next.skills) {
115
+ const rel = relative(layout.legacySkills, resolve(skill.rootPath));
116
+ if (isAbsolute(rel) || rel === '..' || rel.startsWith('../') || rel.startsWith('..\\') || resolve(layout.legacySkills, rel) !== resolve(skill.rootPath))
117
+ throw Error('Skill 不在已登记的公共目录中');
118
+ skill.rootPath = join(layout.publicRoot, 'skills', rel);
119
+ }
120
+ const split = new SplitStatePersistence(staging(layout.publicRoot), privateStage);
121
+ await split.write(next);
122
+ if (JSON.stringify(await split.read()) !== JSON.stringify(next)) {
123
+ // Field/array order can change during partition; compare canonical state below.
124
+ if (canonical(await split.read()) !== canonical(next))
125
+ throw Error('公共和私有配置拆分校验失败');
126
+ }
127
+ const inbox = await PartnerInboxStore.openPartitioned(join(staging(layout.publicRoot), 'indexes', 'inbox.sqlite'), privateStage);
128
+ try {
129
+ for (const notice of notices) {
130
+ inbox.append(notice);
131
+ if (notice.readAt !== undefined)
132
+ inbox.markRead([notice.id], notice.readAt);
133
+ }
134
+ }
135
+ finally {
136
+ inbox.close();
137
+ }
138
+ const attachments = await AttachmentDeliveryService.openPartitioned(join(staging(layout.publicRoot), 'indexes', 'attachments'), privateStage);
139
+ try {
140
+ for (const item of deliveries) {
141
+ if (!/^[a-f0-9]{64}$/.test(item.id))
142
+ throw Error('附件迁移标识无效');
143
+ await safeTree(join(deliveryRoot, item.id));
144
+ await attachments.importExisting(item, await readFile(join(deliveryRoot, item.id)));
145
+ }
146
+ }
147
+ finally {
148
+ attachments.close();
149
+ }
150
+ for (const target of targets)
151
+ await verifyDatabases(staging(target));
152
+ for (const target of targets) {
153
+ await rename(staging(target), target);
154
+ await syncDirectory(dirname(target));
155
+ }
156
+ await atomicJson(journalPath, { ...journal, phase: 'installed' });
157
+ return { storageVersion: 1, backupPath: backup, retainedLegacy: true };
158
+ }
159
+ export async function verifyInstalledLayout(context) {
160
+ const layout = storageLayout(resolve(context.statePath), resolve(context.root));
161
+ const journal = JSON.parse(await readFile(join(layout.legacyPublic, 'storage-migration.json'), 'utf8'));
162
+ if (journal.phase !== 'installed')
163
+ throw Error('迁移尚未完成安装');
164
+ await new SplitStatePersistence(layout.publicRoot, layout.privateRoot).read();
165
+ for (const target of [layout.publicRoot, ...journal.owners.map(id => layout.privateRoot(id))]) {
166
+ const marker = JSON.parse(await readFile(join(target, 'migration-owner.json'), 'utf8'));
167
+ if (marker.transactionId !== journal.id)
168
+ throw Error('迁移目标标记不一致');
169
+ await verifyDatabases(target);
170
+ }
171
+ }
172
+ export async function commitLayoutVersion(context, expected, next) {
173
+ if (expected !== 0 || next !== 1)
174
+ throw Error('布局迁移版本无效');
175
+ const root = resolve(context.root), statePath = resolve(context.statePath), layout = storageLayout(statePath, root);
176
+ const current = await readStorageConfig(statePath, root);
177
+ if (current.storageVersion !== expected)
178
+ throw Error('迁移版本已被其他操作更新');
179
+ const journalPath = join(layout.legacyPublic, 'storage-migration.json');
180
+ const journal = JSON.parse(await readFile(journalPath, 'utf8'));
181
+ if (journal.phase !== 'installed')
182
+ throw Error('没有可提交的迁移');
183
+ await atomicJson(storageConfigPath(statePath), { storageVersion: next, transactionId: journal.id, workspaceRoot: root });
184
+ await atomicJson(journalPath, { ...journal, phase: 'committed' }).catch(() => { });
185
+ // Activation is durable. Cleanup failures must not be reported as a rollback.
186
+ try {
187
+ const cleanup = await cleanupArchivedSources(statePath, root, journal.id);
188
+ if (context.result) {
189
+ context.result.retainedLegacy = cleanup.pending > 0;
190
+ context.result.cleanupPending = cleanup.pending;
191
+ }
192
+ }
193
+ catch {
194
+ if (context.result) {
195
+ context.result.retainedLegacy = true;
196
+ context.result.cleanupPending = 1;
197
+ }
198
+ }
199
+ }
200
+ function canonical(value) {
201
+ const normalize = (v) => Array.isArray(v) ? v.map(normalize).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))) : v && typeof v === 'object' ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([k, x]) => [k, normalize(x)])) : v;
202
+ return JSON.stringify(normalize(value));
203
+ }
204
+ //# sourceMappingURL=migrate-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-layout.js","sourceRoot":"","sources":["../../src/storage/migrate-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { migrationPath, runMigrationPath } from './registry.js';
2
+ export declare const storageMigrations: readonly [import("./registry.js").StorageMigration<import("../migrate-layout.js").LayoutMigrationContext>];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/migrations/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC/D,eAAO,MAAM,iBAAiB,4GAAoB,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { v0ToV1 } from './v0-to-v1.js';
2
+ export { migrationPath, runMigrationPath } from './registry.js';
3
+ export const storageMigrations = [v0ToV1];
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/storage/migrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC/D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAM,CAAU,CAAA"}
@@ -0,0 +1,19 @@
1
+ /** A version is a complete public + private layout transition, never one
2
+ * companion. The coordinator supplies the exclusive gate and durable journal.
3
+ */
4
+ export interface StorageMigration<Context> {
5
+ id: string;
6
+ from: number;
7
+ to: number;
8
+ title: string;
9
+ /** Absent while a step is inspection-only; do not expose an execute button. */
10
+ execute?: (context: Context) => Promise<void>;
11
+ verify?: (context: Context) => Promise<void>;
12
+ }
13
+ export declare function migrationPath<Context>(steps: readonly StorageMigration<Context>[], from: number, to: number): StorageMigration<Context>[];
14
+ /** The caller must hold its runtime write gate throughout. On any error stop:
15
+ * reopen the persisted version/journal before retrying (including commit errors).
16
+ * This runner does not guess whether a failed commit reached durable storage.
17
+ */
18
+ export declare function runMigrationPath<Context>(steps: readonly StorageMigration<Context>[], from: number, to: number, context: Context, commitVersion: (expected: number, next: number, migrationId: string) => Promise<void>): Promise<void>;
19
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/storage/migrations/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,OAAO;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,gBAAgB,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAiBzI;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,gBAAgB,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAS7O"}
@@ -0,0 +1,40 @@
1
+ export function migrationPath(steps, from, to) {
2
+ if (![from, to].every(version => Number.isSafeInteger(version) && version >= 0))
3
+ throw Error('迁移版本必须是非负整数');
4
+ if (from > to)
5
+ throw Error('不支持自动降级数据目录');
6
+ const bySource = new Map();
7
+ const ids = new Set();
8
+ for (const step of steps) {
9
+ if (!step.id || ids.has(step.id) || bySource.has(step.from))
10
+ throw Error('迁移版本注册重复');
11
+ if (!Number.isSafeInteger(step.from) || step.from < 0 || step.to !== step.from + 1)
12
+ throw Error('迁移必须逐版本递增');
13
+ ids.add(step.id);
14
+ bySource.set(step.from, step);
15
+ }
16
+ const result = [];
17
+ for (let version = from; version < to; version++) {
18
+ const step = bySource.get(version);
19
+ if (!step)
20
+ throw Error(`缺少 v${version} → v${version + 1} 迁移脚本`);
21
+ result.push(step);
22
+ }
23
+ return result;
24
+ }
25
+ /** The caller must hold its runtime write gate throughout. On any error stop:
26
+ * reopen the persisted version/journal before retrying (including commit errors).
27
+ * This runner does not guess whether a failed commit reached durable storage.
28
+ */
29
+ export async function runMigrationPath(steps, from, to, context, commitVersion) {
30
+ const path = migrationPath(steps, from, to);
31
+ // Refuse incomplete chains before touching even the first step.
32
+ if (path.some(step => !step.execute || !step.verify))
33
+ throw Error('迁移链尚未全部实现,拒绝执行');
34
+ for (const step of path) {
35
+ await step.execute(context);
36
+ await step.verify(context);
37
+ await commitVersion(step.from, step.to, step.id);
38
+ }
39
+ }
40
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/storage/migrations/registry.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,aAAa,CAAU,KAA2C,EAAE,IAAY,EAAE,EAAU;IAC1G,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC;QAAE,MAAM,KAAK,CAAC,aAAa,CAAC,CAAA;IAC3G,IAAI,IAAI,GAAG,EAAE;QAAE,MAAM,KAAK,CAAC,aAAa,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqC,CAAA;IAC7D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,KAAK,CAAC,UAAU,CAAC,CAAA;QACpF,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC;YAAE,MAAM,KAAK,CAAC,WAAW,CAAC,CAAA;QAC5G,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACjD,CAAC;IACD,MAAM,MAAM,GAAgC,EAAE,CAAA;IAC9C,KAAK,IAAI,OAAO,GAAG,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,CAAC,IAAI;YAAE,MAAM,KAAK,CAAC,OAAO,OAAO,OAAO,OAAO,GAAG,CAAC,OAAO,CAAC,CAAA;QAC/D,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnB,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAU,KAA2C,EAAE,IAAY,EAAE,EAAU,EAAE,OAAgB,EAAE,aAAqF;IAC5N,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IAC3C,gEAAgE;IAChE,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAA;IACnF,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,CAAC,OAAQ,CAAC,OAAO,CAAC,CAAA;QAC5B,MAAM,IAAI,CAAC,MAAO,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;IAClD,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { StorageMigration } from './registry.js';
2
+ import { type LayoutMigrationContext } from '../migrate-layout.js';
3
+ /** Owns only the v0 -> v1 transition. Later layouts get separate modules. */
4
+ export declare const v0ToV1: StorageMigration<LayoutMigrationContext>;
5
+ //# sourceMappingURL=v0-to-v1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v0-to-v1.d.ts","sourceRoot":"","sources":["../../../src/storage/migrations/v0-to-v1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAwC,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAExG,6EAA6E;AAC7E,eAAO,MAAM,MAAM,EAAE,gBAAgB,CAAC,sBAAsB,CAO3D,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { migrateLayout, verifyInstalledLayout } from '../migrate-layout.js';
2
+ /** Owns only the v0 -> v1 transition. Later layouts get separate modules. */
3
+ export const v0ToV1 = {
4
+ id: 'v0-to-v1-private-public-layout',
5
+ from: 0,
6
+ to: 1,
7
+ title: '公共数据归档与伙伴 .partner 私有目录',
8
+ execute: async (context) => { context.result = await migrateLayout(context.statePath, context.root, context.state); },
9
+ verify: verifyInstalledLayout,
10
+ };
11
+ //# sourceMappingURL=v0-to-v1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v0-to-v1.js","sourceRoot":"","sources":["../../../src/storage/migrations/v0-to-v1.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAA+B,MAAM,sBAAsB,CAAA;AAExG,6EAA6E;AAC7E,MAAM,CAAC,MAAM,MAAM,GAA6C;IAC9D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,CAAC;IACP,EAAE,EAAE,CAAC;IACL,KAAK,EAAE,yBAAyB;IAChC,OAAO,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE,GAAG,OAAO,CAAC,MAAM,GAAC,MAAM,aAAa,CAAC,OAAO,CAAC,SAAS,EAAC,OAAO,CAAC,IAAI,EAAC,OAAO,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC;IAC9G,MAAM,EAAE,qBAAqB;CAC9B,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { PartnerInboxStore } from '../notifications/store.js';
2
+ /** Startup-only, local system notice. No model, channel delivery or directory scan. */
3
+ export declare function syncStorageNotice(inbox: PartnerInboxStore, current: number, target: number): void;
4
+ //# sourceMappingURL=notice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notice.d.ts","sourceRoot":"","sources":["../../src/storage/notice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAElE,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CASjG"}
@@ -0,0 +1,13 @@
1
+ /** Startup-only, local system notice. No model, channel delivery or directory scan. */
2
+ export function syncStorageNotice(inbox, current, target) {
3
+ const id = `system:storage-migration:${target}`;
4
+ const pending = current < target;
5
+ const previous = inbox.snapshot().items.filter(item => item.kind === 'system' && item.action === 'storage-migration');
6
+ inbox.remove(previous.filter(item => !pending || item.id !== id).map(item => item.id));
7
+ if (!pending)
8
+ return;
9
+ inbox.append({ id, kind: 'system', action: 'storage-migration', companionId: '', companionName: '伙伴插件',
10
+ title: '需要升级数据目录',
11
+ summary: `当前存储版本 **${current}**,可升级到 **${target}**。\n\n升级会整理公共数据及各伙伴的私有目录。请先备份,再进入「设置 → 基本设置 → 升级迁移」检查并确认。\n\n**不会自动迁移**,点击下方「前往升级迁移」打开设置。`, createdAt: Date.now() });
12
+ }
13
+ //# sourceMappingURL=notice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notice.js","sourceRoot":"","sources":["../../src/storage/notice.ts"],"names":[],"mappings":"AAEA,uFAAuF;AACvF,MAAM,UAAU,iBAAiB,CAAC,KAAwB,EAAE,OAAe,EAAE,MAAc;IACzF,MAAM,EAAE,GAAG,4BAA4B,MAAM,EAAE,CAAA;IAC/C,MAAM,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;IAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,mBAAmB,CAAC,CAAA;IACrH,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACtF,IAAI,CAAC,OAAO;QAAE,OAAM;IACpB,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,WAAW,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM;QACpG,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,YAAY,OAAO,aAAa,MAAM,4FAA4F,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AACzK,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { PartnerState } from '../domain.js';
2
+ export interface StorageCheckItem {
3
+ label: string;
4
+ source: string;
5
+ target: string;
6
+ files: number;
7
+ bytes: number;
8
+ exists: boolean;
9
+ }
10
+ export interface StorageInspection {
11
+ currentVersion: number;
12
+ targetVersion: number;
13
+ migrationAvailable: boolean;
14
+ consistentSnapshot: false;
15
+ items: StorageCheckItem[];
16
+ blockers: string[];
17
+ notices: string[];
18
+ checkedAt: number;
19
+ steps: {
20
+ id: string;
21
+ from: number;
22
+ to: number;
23
+ title: string;
24
+ available: boolean;
25
+ }[];
26
+ }
27
+ /** Read-only inventory. Never opens SQLite or reads config/credential payloads.
28
+ * Live sizes are estimates; the eventual migration must recheck under a gate.
29
+ */
30
+ export declare class StoragePreflight {
31
+ private readonly statePath;
32
+ private readonly root;
33
+ private readonly version;
34
+ private readonly snapshot;
35
+ private pending;
36
+ constructor(statePath: string, root: string, version: number, snapshot: () => PartnerState);
37
+ inspect(): Promise<StorageInspection>;
38
+ private scan;
39
+ }
40
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/storage/preflight.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAKhD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;CAChB;AACD,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,kBAAkB,EAAE,KAAK,CAAA;IACzB,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CACrF;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAEf,OAAO,CAAC,QAAQ,CAAC,SAAS;IAAU,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAU,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAD1I,OAAO,CAAC,OAAO,CAAwC;gBAC1B,SAAS,EAAE,MAAM,EAAmB,IAAI,EAAE,MAAM,EAAmB,OAAO,EAAE,MAAM,EAAmB,QAAQ,EAAE,MAAM,YAAY;IAE9J,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC;YAOvB,IAAI;CAmFnB"}
@@ -0,0 +1,208 @@
1
+ import { access, lstat, readdir, readFile, statfs } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { storageLayout, storageVersion, TARGET_STORAGE_VERSION } from './layout.js';
5
+ import { migrationPath, storageMigrations } from './migrations/index.js';
6
+ import { legacyMemoryBackups } from './legacy-backups.js';
7
+ /** Read-only inventory. Never opens SQLite or reads config/credential payloads.
8
+ * Live sizes are estimates; the eventual migration must recheck under a gate.
9
+ */
10
+ export class StoragePreflight {
11
+ statePath;
12
+ root;
13
+ version;
14
+ snapshot;
15
+ pending;
16
+ constructor(statePath, root, version, snapshot) {
17
+ this.statePath = statePath;
18
+ this.root = root;
19
+ this.version = version;
20
+ this.snapshot = snapshot;
21
+ }
22
+ inspect() {
23
+ if (this.pending)
24
+ return this.pending;
25
+ const work = this.scan().finally(() => { if (this.pending === work)
26
+ this.pending = undefined; });
27
+ this.pending = work;
28
+ return work;
29
+ }
30
+ async scan() {
31
+ const state = this.snapshot();
32
+ const layout = storageLayout(resolve(this.statePath), resolve(this.root));
33
+ const result = {
34
+ currentVersion: storageVersion(this.version), targetVersion: TARGET_STORAGE_VERSION,
35
+ migrationAvailable: this.version < TARGET_STORAGE_VERSION, consistentSnapshot: false, items: [], blockers: [],
36
+ steps: migrationPath(storageMigrations, this.version, TARGET_STORAGE_VERSION).map(step => ({ id: step.id, from: step.from, to: step.to, title: step.title, available: !!step.execute && !!step.verify })),
37
+ notices: ['检查不会修改配置或数据库;运行中的文件数量与大小仅作预估。', '版本由迁移流程维护,请勿手动修改;公共和私有数据全部校验后才提交版本。', '凭据、宿主会话与知识库正文不搬动;工作文件和记录文档保持原位。'], checkedAt: Date.now(),
38
+ };
39
+ if (this.version === TARGET_STORAGE_VERSION) {
40
+ result.notices.push(`已使用新版公共目录:${layout.publicRoot}`);
41
+ return result;
42
+ }
43
+ let recoveryId;
44
+ try {
45
+ const journal = JSON.parse(await readFile(join(layout.legacyPublic, 'storage-migration.json'), 'utf8'));
46
+ if (typeof journal.id === 'string')
47
+ recoveryId = journal.id;
48
+ }
49
+ catch (error) {
50
+ if (!missing(error))
51
+ result.blockers.push('迁移恢复记录无法读取');
52
+ }
53
+ let budget = 50_000;
54
+ const inspectTree = async (path, device) => {
55
+ if (--budget < 0)
56
+ throw Error('文件数量超过检查上限,请先核对目录范围');
57
+ let entry;
58
+ try {
59
+ entry = await lstat(path);
60
+ }
61
+ catch (error) {
62
+ if (missing(error))
63
+ return { exists: false, files: 0, bytes: 0 };
64
+ throw error;
65
+ }
66
+ if (entry.isSymbolicLink())
67
+ throw Error('包含符号链接,需人工确认');
68
+ if (device !== undefined && entry.dev !== device)
69
+ throw Error('跨文件系统目录,需人工确认');
70
+ if (entry.isFile()) {
71
+ await access(path, constants.R_OK);
72
+ return { exists: true, files: 1, bytes: entry.size };
73
+ }
74
+ if (!entry.isDirectory())
75
+ throw Error('包含特殊文件,需人工确认');
76
+ let files = 0, bytes = 0;
77
+ for (const child of await readdir(path)) {
78
+ const value = await inspectTree(join(path, child), entry.dev);
79
+ files += value.files;
80
+ bytes += value.bytes;
81
+ }
82
+ return { exists: true, files, bytes };
83
+ };
84
+ const inspect = async (label, source, target) => {
85
+ try {
86
+ await assertAncestors(source);
87
+ const value = await inspectTree(source);
88
+ result.items.push({ label, source, target, ...value });
89
+ }
90
+ catch (error) {
91
+ result.blockers.push(`${label}:${safeError(error)}`);
92
+ }
93
+ };
94
+ const targets = [layout.publicRoot];
95
+ await inspect('公共配置(待拆分私有字段)', resolve(this.statePath), join(layout.publicRoot, 'state'));
96
+ await inspect('Skill 安装库', layout.legacySkills, join(layout.publicRoot, 'skills'));
97
+ await inspect('消息索引(待按伙伴拆分)', join(layout.legacyPublic, 'partner-inbox.sqlite'), join(layout.publicRoot, 'indexes'));
98
+ await inspect('附件交付(待按伙伴拆分)', join(layout.legacyPublic, 'attachment-deliveries'), join(layout.publicRoot, 'indexes'));
99
+ const seen = new Set();
100
+ for (const companion of state.companions) {
101
+ if (seen.has(companion.id)) {
102
+ result.blockers.push('伙伴 ID 重复,不能迁移');
103
+ continue;
104
+ }
105
+ seen.add(companion.id);
106
+ let source, target;
107
+ try {
108
+ source = layout.legacyCompanion(companion.id);
109
+ target = layout.privateRoot(companion.id);
110
+ }
111
+ catch {
112
+ result.blockers.push('伙伴 ID 无效,不能迁移');
113
+ continue;
114
+ }
115
+ targets.push(target);
116
+ await inspect(`${companion.name} · 记忆`, join(source, 'memory'), join(target, 'memory'));
117
+ await inspect(`${companion.name} · 关注`, join(source, 'concerns'), join(target, 'concerns'));
118
+ try {
119
+ for (const name of await legacyMemoryBackups(source)) {
120
+ await inspect(`${companion.name} · ${name}`, join(source, name), join(target, 'backups', 'legacy-memory', name));
121
+ result.notices.push(`${companion.name} · ${name} 原样归档,不合并到当前记忆`);
122
+ }
123
+ }
124
+ catch (error) {
125
+ if (!missing(error))
126
+ result.blockers.push(`${companion.name} · 备份检查:${safeError(error)}`);
127
+ }
128
+ }
129
+ const total = result.items.reduce((sum, item) => sum + item.bytes, 0);
130
+ for (const target of targets) {
131
+ try {
132
+ await assertAncestors(target);
133
+ let ancestor = target;
134
+ try {
135
+ await lstat(target);
136
+ let owned = false;
137
+ if (recoveryId)
138
+ try {
139
+ const marker = JSON.parse(await readFile(join(target, 'migration-owner.json'), 'utf8'));
140
+ owned = marker.transactionId === recoveryId;
141
+ }
142
+ catch { }
143
+ if (!owned)
144
+ result.blockers.push(`目标目录已存在,不允许覆盖:${target}`);
145
+ else
146
+ result.notices.push(`发现未提交的迁移,可安全重试:${target}`);
147
+ }
148
+ catch (error) {
149
+ if (!missing(error))
150
+ throw error;
151
+ }
152
+ while (true) {
153
+ try {
154
+ await lstat(ancestor);
155
+ break;
156
+ }
157
+ catch (error) {
158
+ if (!missing(error))
159
+ throw error;
160
+ ancestor = dirname(ancestor);
161
+ }
162
+ }
163
+ await access(ancestor, constants.W_OK | constants.X_OK);
164
+ const fs = await statfs(ancestor);
165
+ // Deliberately conservative until an exact per-volume copy plan exists.
166
+ if (fs.bavail * fs.bsize < total * 3)
167
+ result.blockers.push(`目标磁盘可用空间不足(含完整备份):${target}`);
168
+ }
169
+ catch (error) {
170
+ result.blockers.push(`目标目录 ${target}:${safeError(error)}`);
171
+ }
172
+ }
173
+ result.notices.push(`已识别 ${state.companions.length} 位伙伴;私有配置还需从公共 state.json 按归属拆分,不是整份复制到每个伙伴。`);
174
+ result.migrationAvailable = result.blockers.length === 0;
175
+ return result;
176
+ }
177
+ }
178
+ async function assertAncestors(path) {
179
+ const paths = [];
180
+ for (let current = resolve(path);;) {
181
+ paths.push(current);
182
+ const parent = dirname(current);
183
+ if (parent === current)
184
+ break;
185
+ current = parent;
186
+ }
187
+ for (const current of paths.reverse()) {
188
+ try {
189
+ if ((await lstat(current)).isSymbolicLink())
190
+ throw Error('路径含符号链接,需人工确认');
191
+ }
192
+ catch (error) {
193
+ if (missing(error))
194
+ return;
195
+ throw error;
196
+ }
197
+ }
198
+ }
199
+ function missing(error) { return error?.code === 'ENOENT'; }
200
+ function safeError(error) {
201
+ const code = error?.code;
202
+ if (code === 'EACCES' || code === 'EPERM')
203
+ return '目录读写权限不足';
204
+ if (code)
205
+ return `文件检查失败(${code})`;
206
+ return error instanceof Error ? error.message : '检查失败';
207
+ }
208
+ //# sourceMappingURL=preflight.js.map