agl 22.0.0 → 22.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +10 -0
- package/dist_serve/bundle.js +520 -509
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.authstore.d.ts +1 -2
- package/dist_ts/classes.authstore.js +1 -6
- package/dist_ts/classes.controller.js +46 -21
- package/dist_ts_migration/classes.documentmigrationrunner.js +3 -1
- package/dist_ts_migration/index.d.ts +2 -0
- package/dist_ts_migration/index.js +3 -1
- package/dist_ts_migration/v24_legacyterminallayout.d.ts +18 -0
- package/dist_ts_migration/v24_legacyterminallayout.js +79 -0
- package/dist_ts_migration/v24_legacyterminallayoutmigration.d.ts +6 -0
- package/dist_ts_migration/v24_legacyterminallayoutmigration.js +67 -0
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.authstore.ts +0 -15
- package/ts/classes.controller.ts +54 -26
- package/ts_migration/classes.documentmigrationrunner.ts +2 -0
- package/ts_migration/index.ts +2 -0
- package/ts_migration/v24_legacyterminallayout.ts +131 -0
- package/ts_migration/v24_legacyterminallayoutmigration.ts +88 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements.harnesscontrollerapp.ts +32 -2
|
@@ -3,6 +3,7 @@ import { ProtocolV6DocumentMigration } from './v6_protocoldocumentmigration.js';
|
|
|
3
3
|
import { ProtocolV18ResourceLayoutMigration } from './v18_resourcelayoutmigration.js';
|
|
4
4
|
import { ProtocolV22ProjectFilesystemIdentityMigration } from './v22_projectfilesystemidentitymigration.js';
|
|
5
5
|
import { RuntimeConfigV23Migration } from './v23_runtimeconfig.js';
|
|
6
|
+
import { LegacyTerminalLayoutV24Migration } from './v24_legacyterminallayoutmigration.js';
|
|
6
7
|
export class PostDatabaseDocumentMigrationRunner {
|
|
7
8
|
database;
|
|
8
9
|
migrations;
|
|
@@ -10,6 +11,7 @@ export class PostDatabaseDocumentMigrationRunner {
|
|
|
10
11
|
this.database = database;
|
|
11
12
|
this.migrations = [
|
|
12
13
|
new RuntimeConfigV23Migration(this.database),
|
|
14
|
+
new LegacyTerminalLayoutV24Migration(this.database),
|
|
13
15
|
new ProtocolV6DocumentMigration(this.database),
|
|
14
16
|
new ProtocolV18ResourceLayoutMigration(this.database),
|
|
15
17
|
];
|
|
@@ -23,4 +25,4 @@ export class PostDatabaseDocumentMigrationRunner {
|
|
|
23
25
|
await new ProtocolV22ProjectFilesystemIdentityMigration(this.database, controllerIdArg).run();
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
28
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kb2N1bWVudG1pZ3JhdGlvbnJ1bm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX21pZ3JhdGlvbi9jbGFzc2VzLmRvY3VtZW50bWlncmF0aW9ucnVubmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLDJCQUEyQixFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDaEYsT0FBTyxFQUFFLGtDQUFrQyxFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFDdEYsT0FBTyxFQUFFLDZDQUE2QyxFQUFFLE1BQU0sNkNBQTZDLENBQUM7QUFDNUcsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDbkUsT0FBTyxFQUFFLGdDQUFnQyxFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFNMUYsTUFBTSxPQUFPLG1DQUFtQztJQUdqQjtJQUZaLFVBQVUsQ0FBdUI7SUFFbEQsWUFBNkIsUUFBbUQ7UUFBbkQsYUFBUSxHQUFSLFFBQVEsQ0FBMkM7UUFDOUUsSUFBSSxDQUFDLFVBQVUsR0FBRztZQUNoQixJQUFJLHlCQUF5QixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUM7WUFDNUMsSUFBSSxnQ0FBZ0MsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDO1lBQ25ELElBQUksMkJBQTJCLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQztZQUM5QyxJQUFJLGtDQUFrQyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUM7U0FDdEQsQ0FBQztJQUNKLENBQUM7SUFFTSxLQUFLLENBQUMsR0FBRztRQUNkLEtBQUssTUFBTSxTQUFTLElBQUksSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQ3hDLE1BQU0sU0FBUyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3hCLENBQUM7SUFDSCxDQUFDO0lBRU0sS0FBSyxDQUFDLDRCQUE0QixDQUFDLGVBQXVCO1FBQy9ELE1BQU0sSUFBSSw2Q0FBNkMsQ0FDckQsSUFBSSxDQUFDLFFBQVEsRUFDYixlQUFlLENBQ2hCLENBQUMsR0FBRyxFQUFFLENBQUM7SUFDVixDQUFDO0NBQ0YifQ==
|
|
@@ -12,3 +12,5 @@ export * from './v19_flexprojectmanagement.js';
|
|
|
12
12
|
export * from './v22_projectfilesystemidentitymigration.js';
|
|
13
13
|
export * from './v23_runtimeconfig.js';
|
|
14
14
|
export * from './v23_aglhome.js';
|
|
15
|
+
export * from './v24_legacyterminallayout.js';
|
|
16
|
+
export * from './v24_legacyterminallayoutmigration.js';
|
|
@@ -12,4 +12,6 @@ export * from './v19_flexprojectmanagement.js';
|
|
|
12
12
|
export * from './v22_projectfilesystemidentitymigration.js';
|
|
13
13
|
export * from './v23_runtimeconfig.js';
|
|
14
14
|
export * from './v23_aglhome.js';
|
|
15
|
-
|
|
15
|
+
export * from './v24_legacyterminallayout.js';
|
|
16
|
+
export * from './v24_legacyterminallayoutmigration.js';
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19taWdyYXRpb24vaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyw4QkFBOEIsQ0FBQztBQUM3QyxjQUFjLGtDQUFrQyxDQUFDO0FBQ2pELGNBQWMsa0NBQWtDLENBQUM7QUFDakQsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLGlDQUFpQyxDQUFDO0FBQ2hELGNBQWMsOEJBQThCLENBQUM7QUFDN0MsY0FBYyxrQ0FBa0MsQ0FBQztBQUNqRCxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsa0NBQWtDLENBQUM7QUFDakQsY0FBYyw2Q0FBNkMsQ0FBQztBQUM1RCxjQUFjLGdDQUFnQyxDQUFDO0FBQy9DLGNBQWMsNkNBQTZDLENBQUM7QUFDNUQsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYyx3Q0FBd0MsQ0FBQyJ9
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface IV24LegacySessionGroup {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
sessionIds: unknown[];
|
|
5
|
+
}
|
|
6
|
+
export interface IV24LegacySessionGroupsDocument {
|
|
7
|
+
id: string;
|
|
8
|
+
controllerId: string;
|
|
9
|
+
projectId: string;
|
|
10
|
+
groups: IV24LegacySessionGroup[];
|
|
11
|
+
ungroupedSessionIds?: unknown[];
|
|
12
|
+
revision?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface IV24LayoutMigrationResult {
|
|
15
|
+
document: IV24LegacySessionGroupsDocument;
|
|
16
|
+
migrated: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const migrateV24LegacyTerminalLayoutDocument: (valueArg: unknown) => IV24LayoutMigrationResult;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const legacyOpenCodePtyIdPattern = /^pty_[0-9a-f]{12}[0-9A-Za-z]{14}$/;
|
|
2
|
+
const legacyControllerTerminalIdPattern = /^terminal_[A-Za-z0-9_-]{22}$/;
|
|
3
|
+
const isPlainObject = (valueArg) => {
|
|
4
|
+
if (typeof valueArg !== 'object' || valueArg === null || Array.isArray(valueArg))
|
|
5
|
+
return false;
|
|
6
|
+
const prototype = Object.getPrototypeOf(valueArg);
|
|
7
|
+
return prototype === Object.prototype || prototype === null;
|
|
8
|
+
};
|
|
9
|
+
const hasExactKeys = (valueArg, requiredArg, optionalArg = []) => {
|
|
10
|
+
const allowed = new Set([...requiredArg, ...optionalArg]);
|
|
11
|
+
return requiredArg.every((key) => Object.hasOwn(valueArg, key))
|
|
12
|
+
&& Object.keys(valueArg).every((key) => allowed.has(key));
|
|
13
|
+
};
|
|
14
|
+
const isCanonicalControllerTerminalId = (valueArg) => {
|
|
15
|
+
if (!legacyControllerTerminalIdPattern.test(valueArg))
|
|
16
|
+
return false;
|
|
17
|
+
const suffix = valueArg.slice('terminal_'.length);
|
|
18
|
+
const decoded = Buffer.from(suffix, 'base64url');
|
|
19
|
+
return decoded.length === 16 && decoded.toString('base64url') === suffix;
|
|
20
|
+
};
|
|
21
|
+
const isLegacyTerminalNativeId = (valueArg) => (typeof valueArg === 'string'
|
|
22
|
+
&& (legacyOpenCodePtyIdPattern.test(valueArg)
|
|
23
|
+
|| isCanonicalControllerTerminalId(valueArg)));
|
|
24
|
+
const isLegacyTerminalLayoutReference = (valueArg) => {
|
|
25
|
+
if (typeof valueArg === 'string')
|
|
26
|
+
return isLegacyTerminalNativeId(valueArg);
|
|
27
|
+
if (!isPlainObject(valueArg)
|
|
28
|
+
|| Object.keys(valueArg).length !== 2
|
|
29
|
+
|| !Object.hasOwn(valueArg, 'harnessId')
|
|
30
|
+
|| !Object.hasOwn(valueArg, 'nativeId')
|
|
31
|
+
|| typeof valueArg.nativeId !== 'string')
|
|
32
|
+
return false;
|
|
33
|
+
if (valueArg.harnessId === 'controller')
|
|
34
|
+
return true;
|
|
35
|
+
return valueArg.harnessId === 'opencode' && isLegacyTerminalNativeId(valueArg.nativeId);
|
|
36
|
+
};
|
|
37
|
+
export const migrateV24LegacyTerminalLayoutDocument = (valueArg) => {
|
|
38
|
+
if (!isPlainObject(valueArg)
|
|
39
|
+
|| !hasExactKeys(valueArg, ['id', 'controllerId', 'projectId', 'groups'], ['ungroupedSessionIds', 'revision'])
|
|
40
|
+
|| !Array.isArray(valueArg.groups)) {
|
|
41
|
+
throw new Error('Invalid legacy terminal layout migration document.');
|
|
42
|
+
}
|
|
43
|
+
const sourceGroups = valueArg.groups;
|
|
44
|
+
const groups = sourceGroups.map((groupArg) => {
|
|
45
|
+
if (!isPlainObject(groupArg)
|
|
46
|
+
|| !hasExactKeys(groupArg, ['id', 'name', 'sessionIds'])
|
|
47
|
+
|| !Array.isArray(groupArg.sessionIds)) {
|
|
48
|
+
throw new Error('Invalid legacy terminal layout group.');
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...groupArg,
|
|
52
|
+
sessionIds: groupArg.sessionIds.filter((sessionId) => (!isLegacyTerminalLayoutReference(sessionId))),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
const hasUngroupedSessionIds = Object.hasOwn(valueArg, 'ungroupedSessionIds');
|
|
56
|
+
if (hasUngroupedSessionIds && !Array.isArray(valueArg.ungroupedSessionIds)) {
|
|
57
|
+
throw new Error('Invalid legacy terminal ungrouped layout list.');
|
|
58
|
+
}
|
|
59
|
+
const ungroupedSessionIds = hasUngroupedSessionIds
|
|
60
|
+
? valueArg.ungroupedSessionIds.filter((sessionId) => (!isLegacyTerminalLayoutReference(sessionId)))
|
|
61
|
+
: undefined;
|
|
62
|
+
const migrated = groups.some((group, index) => (group.sessionIds.length !== sourceGroups[index].sessionIds.length)) || (hasUngroupedSessionIds
|
|
63
|
+
&& ungroupedSessionIds.length !== valueArg.ungroupedSessionIds.length);
|
|
64
|
+
if (!migrated) {
|
|
65
|
+
return {
|
|
66
|
+
document: valueArg,
|
|
67
|
+
migrated: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
document: {
|
|
72
|
+
...valueArg,
|
|
73
|
+
groups,
|
|
74
|
+
...(hasUngroupedSessionIds ? { ungroupedSessionIds } : {}),
|
|
75
|
+
},
|
|
76
|
+
migrated: true,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidjI0X2xlZ2FjeXRlcm1pbmFsbGF5b3V0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfbWlncmF0aW9uL3YyNF9sZWdhY3l0ZXJtaW5hbGxheW91dC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxNQUFNLDBCQUEwQixHQUFHLG1DQUFtQyxDQUFDO0FBQ3ZFLE1BQU0saUNBQWlDLEdBQUcsOEJBQThCLENBQUM7QUFzQnpFLE1BQU0sYUFBYSxHQUFHLENBQUMsUUFBaUIsRUFBdUIsRUFBRTtJQUMvRCxJQUFJLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxRQUFRLEtBQUssSUFBSSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDL0YsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNsRCxPQUFPLFNBQVMsS0FBSyxNQUFNLENBQUMsU0FBUyxJQUFJLFNBQVMsS0FBSyxJQUFJLENBQUM7QUFDOUQsQ0FBQyxDQUFDO0FBRUYsTUFBTSxZQUFZLEdBQUcsQ0FDbkIsUUFBaUIsRUFDakIsV0FBOEIsRUFDOUIsY0FBaUMsRUFBRSxFQUMxQixFQUFFO0lBQ1gsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFHLFdBQVcsRUFBRSxHQUFHLFdBQVcsQ0FBQyxDQUFDLENBQUM7SUFDMUQsT0FBTyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQztXQUMxRCxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQzlELENBQUMsQ0FBQztBQUVGLE1BQU0sK0JB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
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as plugins from '../dist_ts/plugins.js';
|
|
2
|
+
import { assertControllerSessionGroupsDocument, ControllerSessionGroupsModel, } from '../dist_ts/classes.authmodels.js';
|
|
3
|
+
import { AuthError } from '../dist_ts/interfaces.auth.js';
|
|
4
|
+
import { migrateProtocolV6SessionGroupsDocument } from './v6_protocoldocuments.js';
|
|
5
|
+
import { migrateV24LegacyTerminalLayoutDocument } from './v24_legacyterminallayout.js';
|
|
6
|
+
const migrationPageLimit = 128;
|
|
7
|
+
const persistedBodyFromRawDocument = (valueArg) => {
|
|
8
|
+
const body = { ...valueArg };
|
|
9
|
+
delete body._id;
|
|
10
|
+
delete body._smartdataRevision;
|
|
11
|
+
return body;
|
|
12
|
+
};
|
|
13
|
+
const assertProtocolV6NormalizableLayout = (valueArg) => {
|
|
14
|
+
const normalized = migrateProtocolV6SessionGroupsDocument(valueArg);
|
|
15
|
+
assertControllerSessionGroupsDocument(normalized.document);
|
|
16
|
+
};
|
|
17
|
+
export class LegacyTerminalLayoutV24Migration {
|
|
18
|
+
database;
|
|
19
|
+
constructor(database) {
|
|
20
|
+
this.database = database;
|
|
21
|
+
}
|
|
22
|
+
async run() {
|
|
23
|
+
if (!this.database) {
|
|
24
|
+
throw new AuthError('not_initialized', 'The authentication store database is unavailable.');
|
|
25
|
+
}
|
|
26
|
+
const collection = ControllerSessionGroupsModel.collection.mongoDbCollection;
|
|
27
|
+
let lastId;
|
|
28
|
+
while (true) {
|
|
29
|
+
const cursor = collection.find(lastId ? { _id: { $gt: lastId } } : {})
|
|
30
|
+
.sort({ _id: 1 })
|
|
31
|
+
.limit(migrationPageLimit);
|
|
32
|
+
let documents;
|
|
33
|
+
try {
|
|
34
|
+
documents = await cursor.toArray();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await cursor.close();
|
|
38
|
+
}
|
|
39
|
+
for (const raw of documents) {
|
|
40
|
+
const migration = migrateV24LegacyTerminalLayoutDocument(persistedBodyFromRawDocument(raw));
|
|
41
|
+
if (!migration.migrated)
|
|
42
|
+
continue;
|
|
43
|
+
assertProtocolV6NormalizableLayout(migration.document);
|
|
44
|
+
const selector = raw._smartdataRevision === undefined
|
|
45
|
+
? { _id: raw._id, _smartdataRevision: { $exists: false } }
|
|
46
|
+
: { _id: raw._id, _smartdataRevision: raw._smartdataRevision };
|
|
47
|
+
const replaced = await collection.findOneAndReplace(selector, { _id: raw._id, ...migration.document, _smartdataRevision: plugins.crypto.randomUUID() }, { returnDocument: 'after', includeResultMetadata: false, upsert: false });
|
|
48
|
+
if (!replaced) {
|
|
49
|
+
const concurrent = await collection.findOne({ _id: raw._id });
|
|
50
|
+
if (!concurrent) {
|
|
51
|
+
throw new AuthError('concurrent_change', 'The legacy terminal layout migration changed concurrently.');
|
|
52
|
+
}
|
|
53
|
+
const remaining = migrateV24LegacyTerminalLayoutDocument(persistedBodyFromRawDocument(concurrent));
|
|
54
|
+
assertProtocolV6NormalizableLayout(remaining.document);
|
|
55
|
+
if (remaining.migrated) {
|
|
56
|
+
throw new AuthError('concurrent_change', 'The legacy terminal layout migration changed concurrently.');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const last = documents.at(-1);
|
|
61
|
+
if (!last || documents.length < migrationPageLimit)
|
|
62
|
+
break;
|
|
63
|
+
lastId = last._id;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidjI0X2xlZ2FjeXRlcm1pbmFsbGF5b3V0bWlncmF0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfbWlncmF0aW9uL3YyNF9sZWdhY3l0ZXJtaW5hbGxheW91dG1pZ3JhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFDTCxxQ0FBcUMsRUFDckMsNEJBQTRCLEdBQzdCLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBRXJELE9BQU8sRUFBRSxzQ0FBc0MsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQ25GLE9BQU8sRUFBRSxzQ0FBc0MsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBR3ZGLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxDQUFDO0FBRS9CLE1BQU0sNEJBQTRCLEdBQUcsQ0FDbkMsUUFBaUMsRUFDQSxFQUFFO0lBQ25DLE1BQU0sSUFBSSxHQUFHLEVBQUUsR0FBRyxRQUFRLEVBQUUsQ0FBQztJQUM3QixPQUFPLElBQUksQ0FBQyxHQUFHLENBQUM7SUFDaEIsT0FBTyxJQUFJLENBQUMsa0JBQWtCLENBQUM7SUFDL0IsT0FBTyxJQUFrRCxDQUFDO0FBQzVELENBQUMsQ0FBQztBQUVGLE1BQU0sa0NBQWtDLEdBQUcsQ0FDekMsUUFBeUMsRUFDbkMsRUFBRTtJQUNSLE1BQU0sVUFBVSxHQUFHLHNDQUFzQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3BFLHFDQUFxQyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUM3RCxDQUFDLENBQUM7QUFFRixNQUFNLE9BQU8sZ0NBQWdDO0lBQ2Q7SUFBN0IsWUFBNkIsUUFBbUQ7UUFBbkQsYUFBUSxHQUFSLFFBQVEsQ0FBMkM7SUFBRyxDQUFDO0lBRTdFLEtBQUssQ0FBQyxHQUFHO1FBQ2QsSUFBSSxDQUFDLElBQUksQ0FB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
|
package/package.json
CHANGED
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/classes.authstore.ts
CHANGED
|
@@ -47,7 +47,6 @@ import type {
|
|
|
47
47
|
IControllerRuntimeId,
|
|
48
48
|
IControllerSessionGroup,
|
|
49
49
|
IControllerSessionLayout,
|
|
50
|
-
TControllerTerminalId,
|
|
51
50
|
TControllerResourceKind,
|
|
52
51
|
TControllerSessionId,
|
|
53
52
|
IControllerIntelligenceExchange,
|
|
@@ -1946,20 +1945,6 @@ export class SmartDataAuthStore implements IAuthStore {
|
|
|
1946
1945
|
);
|
|
1947
1946
|
}
|
|
1948
1947
|
|
|
1949
|
-
public async pruneTerminalLayoutEntries(
|
|
1950
|
-
projectIdArg: string,
|
|
1951
|
-
liveTerminalIdsArg: readonly TControllerTerminalId[],
|
|
1952
|
-
): Promise<boolean> {
|
|
1953
|
-
const liveTerminalKeys = new Set(liveTerminalIdsArg.map(controllerRuntimeIdKey));
|
|
1954
|
-
return this.filterSessionLayout(
|
|
1955
|
-
projectIdArg,
|
|
1956
|
-
(layoutItemId) => (
|
|
1957
|
-
layoutItemId.harnessId !== 'controller'
|
|
1958
|
-
|| liveTerminalKeys.has(controllerRuntimeIdKey(layoutItemId))
|
|
1959
|
-
),
|
|
1960
|
-
);
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
1948
|
public async pruneArchivedSessionLayoutEntries(
|
|
1964
1949
|
projectIdArg: string,
|
|
1965
1950
|
archivedSessionIdsArg: readonly IControllerRuntimeId[],
|
package/ts/classes.controller.ts
CHANGED
|
@@ -1378,6 +1378,13 @@ interface IControllerHarnessSessionEnumeration {
|
|
|
1378
1378
|
flex: PromiseSettledResult<IControllerSession[]>;
|
|
1379
1379
|
}
|
|
1380
1380
|
|
|
1381
|
+
interface IControllerHarnessSessionEnumerationTask {
|
|
1382
|
+
task: Promise<IControllerHarnessSessionEnumeration>;
|
|
1383
|
+
invalidated: boolean;
|
|
1384
|
+
settled: boolean;
|
|
1385
|
+
waiterCount: number;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1381
1388
|
export class OpenCodeController {
|
|
1382
1389
|
public readonly options: IControllerStartOptions;
|
|
1383
1390
|
|
|
@@ -1509,7 +1516,7 @@ export class OpenCodeController {
|
|
|
1509
1516
|
private readonly activeOperations = new Set<Promise<unknown>>();
|
|
1510
1517
|
private readonly harnessSessionEnumerationTasks = new Map<
|
|
1511
1518
|
string,
|
|
1512
|
-
|
|
1519
|
+
IControllerHarnessSessionEnumerationTask
|
|
1513
1520
|
>();
|
|
1514
1521
|
private readonly sessionLayoutOperationTails = new Map<string, Promise<void>>();
|
|
1515
1522
|
private readonly sessionLayoutOperationDepths = new Map<string, number>();
|
|
@@ -2015,12 +2022,6 @@ export class OpenCodeController {
|
|
|
2015
2022
|
);
|
|
2016
2023
|
const startupProjectState = await this.reconcileProjectState(false);
|
|
2017
2024
|
for (const project of pendingProjectRemovals) this.retiringProjectIds.add(project.id);
|
|
2018
|
-
for (const project of startupProjectState.projects) {
|
|
2019
|
-
await this.runSessionLayoutOperation(
|
|
2020
|
-
project.id,
|
|
2021
|
-
() => authStore.pruneTerminalLayoutEntries(project.id, []),
|
|
2022
|
-
);
|
|
2023
|
-
}
|
|
2024
2025
|
const startupSettings = await authStore.getSettings();
|
|
2025
2026
|
this.defaultModels = startupSettings.defaultModels;
|
|
2026
2027
|
this.autoAcceptPermissions = startupSettings.autoAcceptPermissions === true;
|
|
@@ -7360,31 +7361,55 @@ export class OpenCodeController {
|
|
|
7360
7361
|
}
|
|
7361
7362
|
}
|
|
7362
7363
|
|
|
7363
|
-
private enumerateHarnessSessions(
|
|
7364
|
+
private async enumerateHarnessSessions(
|
|
7364
7365
|
projectIdArg: string,
|
|
7365
7366
|
directoryArg: string,
|
|
7366
7367
|
signalArg: AbortSignal,
|
|
7367
7368
|
): Promise<IControllerHarnessSessionEnumeration> {
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
Promise.
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7369
|
+
while (true) {
|
|
7370
|
+
signalArg.throwIfAborted();
|
|
7371
|
+
let shared = this.harnessSessionEnumerationTasks.get(projectIdArg);
|
|
7372
|
+
if (!shared || shared.invalidated) {
|
|
7373
|
+
const lifecycleSignal = this.operationAbortController.signal;
|
|
7374
|
+
const operation = Promise.allSettled([
|
|
7375
|
+
Promise.resolve().then(() => (
|
|
7376
|
+
this.requireOpenCodeClient().listSessions(directoryArg, lifecycleSignal)
|
|
7377
|
+
)),
|
|
7378
|
+
Promise.resolve().then(() => this.listFlexSessions(projectIdArg, lifecycleSignal)),
|
|
7379
|
+
]).then(([openCode, flex]) => ({ openCode, flex }));
|
|
7380
|
+
let taskRecord!: IControllerHarnessSessionEnumerationTask;
|
|
7381
|
+
let trackedTask!: Promise<IControllerHarnessSessionEnumeration>;
|
|
7382
|
+
trackedTask = this.trackOperationPromise(operation, () => {
|
|
7383
|
+
taskRecord.settled = true;
|
|
7384
|
+
const current = this.harnessSessionEnumerationTasks.get(projectIdArg);
|
|
7385
|
+
if (current === taskRecord && taskRecord.waiterCount === 0) {
|
|
7386
|
+
this.harnessSessionEnumerationTasks.delete(projectIdArg);
|
|
7387
|
+
}
|
|
7388
|
+
});
|
|
7389
|
+
taskRecord = {
|
|
7390
|
+
task: trackedTask,
|
|
7391
|
+
invalidated: false,
|
|
7392
|
+
settled: false,
|
|
7393
|
+
waiterCount: 0,
|
|
7394
|
+
};
|
|
7395
|
+
shared = taskRecord;
|
|
7396
|
+
this.harnessSessionEnumerationTasks.set(projectIdArg, shared);
|
|
7397
|
+
}
|
|
7398
|
+
shared.waiterCount += 1;
|
|
7399
|
+
try {
|
|
7400
|
+
const result = await this.waitForPromiseWithSignal(shared.task, signalArg);
|
|
7401
|
+
if (!shared.invalidated) return result;
|
|
7402
|
+
} finally {
|
|
7403
|
+
shared.waiterCount -= 1;
|
|
7404
|
+
if (
|
|
7405
|
+
shared.settled
|
|
7406
|
+
&& shared.waiterCount === 0
|
|
7407
|
+
&& this.harnessSessionEnumerationTasks.get(projectIdArg) === shared
|
|
7408
|
+
) {
|
|
7381
7409
|
this.harnessSessionEnumerationTasks.delete(projectIdArg);
|
|
7382
7410
|
}
|
|
7383
|
-
}
|
|
7384
|
-
this.harnessSessionEnumerationTasks.set(projectIdArg, trackedTask);
|
|
7385
|
-
sharedTask = trackedTask;
|
|
7411
|
+
}
|
|
7386
7412
|
}
|
|
7387
|
-
return this.waitForPromiseWithSignal(sharedTask, signalArg);
|
|
7388
7413
|
}
|
|
7389
7414
|
|
|
7390
7415
|
private toFlexSession(
|
|
@@ -11720,7 +11745,7 @@ export class OpenCodeController {
|
|
|
11720
11745
|
signalArg?: AbortSignal,
|
|
11721
11746
|
): Promise<IControllerSession> {
|
|
11722
11747
|
if (sessionIdArg.harnessId === 'opencode') {
|
|
11723
|
-
return this.requireOpenCodeClient().
|
|
11748
|
+
return this.requireOpenCodeClient().getSessionSnapshot(
|
|
11724
11749
|
directoryArg,
|
|
11725
11750
|
sessionIdArg.nativeId,
|
|
11726
11751
|
signalArg,
|
|
@@ -11977,6 +12002,9 @@ export class OpenCodeController {
|
|
|
11977
12002
|
projectIdArg: string,
|
|
11978
12003
|
sessionIdArg: IControllerRuntimeId,
|
|
11979
12004
|
): void {
|
|
12005
|
+
const enumeration = this.harnessSessionEnumerationTasks.get(projectIdArg);
|
|
12006
|
+
if (enumeration) enumeration.invalidated = true;
|
|
12007
|
+
this.harnessSessionEnumerationTasks.delete(projectIdArg);
|
|
11980
12008
|
if (sessionIdArg.harnessId === 'opencode') {
|
|
11981
12009
|
this.purgeOpenCodeStreamStateForSession(projectIdArg, sessionIdArg);
|
|
11982
12010
|
} else if (sessionIdArg.harnessId === 'flex') {
|
|
@@ -3,6 +3,7 @@ import { ProtocolV6DocumentMigration } from './v6_protocoldocumentmigration.js';
|
|
|
3
3
|
import { ProtocolV18ResourceLayoutMigration } from './v18_resourcelayoutmigration.js';
|
|
4
4
|
import { ProtocolV22ProjectFilesystemIdentityMigration } from './v22_projectfilesystemidentitymigration.js';
|
|
5
5
|
import { RuntimeConfigV23Migration } from './v23_runtimeconfig.js';
|
|
6
|
+
import { LegacyTerminalLayoutV24Migration } from './v24_legacyterminallayoutmigration.js';
|
|
6
7
|
|
|
7
8
|
interface IDocumentMigration {
|
|
8
9
|
run(): Promise<void>;
|
|
@@ -14,6 +15,7 @@ export class PostDatabaseDocumentMigrationRunner {
|
|
|
14
15
|
constructor(private readonly database: plugins.smartdata.SmartdataDb | undefined) {
|
|
15
16
|
this.migrations = [
|
|
16
17
|
new RuntimeConfigV23Migration(this.database),
|
|
18
|
+
new LegacyTerminalLayoutV24Migration(this.database),
|
|
17
19
|
new ProtocolV6DocumentMigration(this.database),
|
|
18
20
|
new ProtocolV18ResourceLayoutMigration(this.database),
|
|
19
21
|
];
|
package/ts_migration/index.ts
CHANGED
|
@@ -12,3 +12,5 @@ export * from './v19_flexprojectmanagement.js';
|
|
|
12
12
|
export * from './v22_projectfilesystemidentitymigration.js';
|
|
13
13
|
export * from './v23_runtimeconfig.js';
|
|
14
14
|
export * from './v23_aglhome.js';
|
|
15
|
+
export * from './v24_legacyterminallayout.js';
|
|
16
|
+
export * from './v24_legacyterminallayoutmigration.js';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
type TObject = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
const legacyOpenCodePtyIdPattern = /^pty_[0-9a-f]{12}[0-9A-Za-z]{14}$/;
|
|
4
|
+
const legacyControllerTerminalIdPattern = /^terminal_[A-Za-z0-9_-]{22}$/;
|
|
5
|
+
|
|
6
|
+
export interface IV24LegacySessionGroup {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
sessionIds: unknown[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IV24LegacySessionGroupsDocument {
|
|
13
|
+
id: string;
|
|
14
|
+
controllerId: string;
|
|
15
|
+
projectId: string;
|
|
16
|
+
groups: IV24LegacySessionGroup[];
|
|
17
|
+
ungroupedSessionIds?: unknown[];
|
|
18
|
+
revision?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IV24LayoutMigrationResult {
|
|
22
|
+
document: IV24LegacySessionGroupsDocument;
|
|
23
|
+
migrated: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isPlainObject = (valueArg: unknown): valueArg is TObject => {
|
|
27
|
+
if (typeof valueArg !== 'object' || valueArg === null || Array.isArray(valueArg)) return false;
|
|
28
|
+
const prototype = Object.getPrototypeOf(valueArg);
|
|
29
|
+
return prototype === Object.prototype || prototype === null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const hasExactKeys = (
|
|
33
|
+
valueArg: TObject,
|
|
34
|
+
requiredArg: readonly string[],
|
|
35
|
+
optionalArg: readonly string[] = [],
|
|
36
|
+
): boolean => {
|
|
37
|
+
const allowed = new Set([...requiredArg, ...optionalArg]);
|
|
38
|
+
return requiredArg.every((key) => Object.hasOwn(valueArg, key))
|
|
39
|
+
&& Object.keys(valueArg).every((key) => allowed.has(key));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const isCanonicalControllerTerminalId = (valueArg: string): boolean => {
|
|
43
|
+
if (!legacyControllerTerminalIdPattern.test(valueArg)) return false;
|
|
44
|
+
const suffix = valueArg.slice('terminal_'.length);
|
|
45
|
+
const decoded = Buffer.from(suffix, 'base64url');
|
|
46
|
+
return decoded.length === 16 && decoded.toString('base64url') === suffix;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const isLegacyTerminalNativeId = (valueArg: unknown): valueArg is string => (
|
|
50
|
+
typeof valueArg === 'string'
|
|
51
|
+
&& (
|
|
52
|
+
legacyOpenCodePtyIdPattern.test(valueArg)
|
|
53
|
+
|| isCanonicalControllerTerminalId(valueArg)
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const isLegacyTerminalLayoutReference = (valueArg: unknown): boolean => {
|
|
58
|
+
if (typeof valueArg === 'string') return isLegacyTerminalNativeId(valueArg);
|
|
59
|
+
if (
|
|
60
|
+
!isPlainObject(valueArg)
|
|
61
|
+
|| Object.keys(valueArg).length !== 2
|
|
62
|
+
|| !Object.hasOwn(valueArg, 'harnessId')
|
|
63
|
+
|| !Object.hasOwn(valueArg, 'nativeId')
|
|
64
|
+
|| typeof valueArg.nativeId !== 'string'
|
|
65
|
+
) return false;
|
|
66
|
+
if (valueArg.harnessId === 'controller') return true;
|
|
67
|
+
return valueArg.harnessId === 'opencode' && isLegacyTerminalNativeId(valueArg.nativeId);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const migrateV24LegacyTerminalLayoutDocument = (
|
|
71
|
+
valueArg: unknown,
|
|
72
|
+
): IV24LayoutMigrationResult => {
|
|
73
|
+
if (
|
|
74
|
+
!isPlainObject(valueArg)
|
|
75
|
+
|| !hasExactKeys(
|
|
76
|
+
valueArg,
|
|
77
|
+
['id', 'controllerId', 'projectId', 'groups'],
|
|
78
|
+
['ungroupedSessionIds', 'revision'],
|
|
79
|
+
)
|
|
80
|
+
|| !Array.isArray(valueArg.groups)
|
|
81
|
+
) {
|
|
82
|
+
throw new Error('Invalid legacy terminal layout migration document.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const sourceGroups = valueArg.groups;
|
|
86
|
+
const groups = sourceGroups.map((groupArg) => {
|
|
87
|
+
if (
|
|
88
|
+
!isPlainObject(groupArg)
|
|
89
|
+
|| !hasExactKeys(groupArg, ['id', 'name', 'sessionIds'])
|
|
90
|
+
|| !Array.isArray(groupArg.sessionIds)
|
|
91
|
+
) {
|
|
92
|
+
throw new Error('Invalid legacy terminal layout group.');
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
...groupArg,
|
|
96
|
+
sessionIds: groupArg.sessionIds.filter((sessionId) => (
|
|
97
|
+
!isLegacyTerminalLayoutReference(sessionId)
|
|
98
|
+
)),
|
|
99
|
+
} as unknown as IV24LegacySessionGroup;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const hasUngroupedSessionIds = Object.hasOwn(valueArg, 'ungroupedSessionIds');
|
|
103
|
+
if (hasUngroupedSessionIds && !Array.isArray(valueArg.ungroupedSessionIds)) {
|
|
104
|
+
throw new Error('Invalid legacy terminal ungrouped layout list.');
|
|
105
|
+
}
|
|
106
|
+
const ungroupedSessionIds = hasUngroupedSessionIds
|
|
107
|
+
? (valueArg.ungroupedSessionIds as unknown[]).filter((sessionId) => (
|
|
108
|
+
!isLegacyTerminalLayoutReference(sessionId)
|
|
109
|
+
))
|
|
110
|
+
: undefined;
|
|
111
|
+
const migrated = groups.some((group, index) => (
|
|
112
|
+
group.sessionIds.length !== (sourceGroups[index] as TObject & { sessionIds: unknown[] }).sessionIds.length
|
|
113
|
+
)) || (
|
|
114
|
+
hasUngroupedSessionIds
|
|
115
|
+
&& ungroupedSessionIds!.length !== (valueArg.ungroupedSessionIds as unknown[]).length
|
|
116
|
+
);
|
|
117
|
+
if (!migrated) {
|
|
118
|
+
return {
|
|
119
|
+
document: valueArg as unknown as IV24LegacySessionGroupsDocument,
|
|
120
|
+
migrated: false,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
document: {
|
|
125
|
+
...valueArg,
|
|
126
|
+
groups,
|
|
127
|
+
...(hasUngroupedSessionIds ? { ungroupedSessionIds } : {}),
|
|
128
|
+
} as unknown as IV24LegacySessionGroupsDocument,
|
|
129
|
+
migrated: true,
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as plugins from '../ts/plugins.js';
|
|
2
|
+
import {
|
|
3
|
+
assertControllerSessionGroupsDocument,
|
|
4
|
+
ControllerSessionGroupsModel,
|
|
5
|
+
} from '../ts/classes.authmodels.js';
|
|
6
|
+
import { AuthError } from '../ts/interfaces.auth.js';
|
|
7
|
+
import type { IControllerSessionGroupsDocument } from '../ts/interfaces.projects.js';
|
|
8
|
+
import { migrateProtocolV6SessionGroupsDocument } from './v6_protocoldocuments.js';
|
|
9
|
+
import { migrateV24LegacyTerminalLayoutDocument } from './v24_legacyterminallayout.js';
|
|
10
|
+
import type { IV24LegacySessionGroupsDocument } from './v24_legacyterminallayout.js';
|
|
11
|
+
|
|
12
|
+
const migrationPageLimit = 128;
|
|
13
|
+
|
|
14
|
+
const persistedBodyFromRawDocument = (
|
|
15
|
+
valueArg: Record<string, unknown>,
|
|
16
|
+
): IV24LegacySessionGroupsDocument => {
|
|
17
|
+
const body = { ...valueArg };
|
|
18
|
+
delete body._id;
|
|
19
|
+
delete body._smartdataRevision;
|
|
20
|
+
return body as unknown as IV24LegacySessionGroupsDocument;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const assertProtocolV6NormalizableLayout = (
|
|
24
|
+
valueArg: IV24LegacySessionGroupsDocument,
|
|
25
|
+
): void => {
|
|
26
|
+
const normalized = migrateProtocolV6SessionGroupsDocument(valueArg);
|
|
27
|
+
assertControllerSessionGroupsDocument(normalized.document);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class LegacyTerminalLayoutV24Migration {
|
|
31
|
+
constructor(private readonly database: plugins.smartdata.SmartdataDb | undefined) {}
|
|
32
|
+
|
|
33
|
+
public async run(): Promise<void> {
|
|
34
|
+
if (!this.database) {
|
|
35
|
+
throw new AuthError('not_initialized', 'The authentication store database is unavailable.');
|
|
36
|
+
}
|
|
37
|
+
const collection = ControllerSessionGroupsModel.collection.mongoDbCollection;
|
|
38
|
+
let lastId: plugins.smartdata.TStoredDocument<IControllerSessionGroupsDocument>['_id'] | undefined;
|
|
39
|
+
while (true) {
|
|
40
|
+
const cursor = collection.find(lastId ? { _id: { $gt: lastId } } : {})
|
|
41
|
+
.sort({ _id: 1 })
|
|
42
|
+
.limit(migrationPageLimit);
|
|
43
|
+
let documents: Awaited<ReturnType<typeof cursor.toArray>>;
|
|
44
|
+
try {
|
|
45
|
+
documents = await cursor.toArray();
|
|
46
|
+
} finally {
|
|
47
|
+
await cursor.close();
|
|
48
|
+
}
|
|
49
|
+
for (const raw of documents) {
|
|
50
|
+
const migration = migrateV24LegacyTerminalLayoutDocument(
|
|
51
|
+
persistedBodyFromRawDocument(raw),
|
|
52
|
+
);
|
|
53
|
+
if (!migration.migrated) continue;
|
|
54
|
+
assertProtocolV6NormalizableLayout(migration.document);
|
|
55
|
+
const selector = raw._smartdataRevision === undefined
|
|
56
|
+
? { _id: raw._id, _smartdataRevision: { $exists: false } }
|
|
57
|
+
: { _id: raw._id, _smartdataRevision: raw._smartdataRevision };
|
|
58
|
+
const replaced = await collection.findOneAndReplace(
|
|
59
|
+
selector,
|
|
60
|
+
{ _id: raw._id, ...migration.document, _smartdataRevision: plugins.crypto.randomUUID() },
|
|
61
|
+
{ returnDocument: 'after', includeResultMetadata: false, upsert: false },
|
|
62
|
+
);
|
|
63
|
+
if (!replaced) {
|
|
64
|
+
const concurrent = await collection.findOne({ _id: raw._id });
|
|
65
|
+
if (!concurrent) {
|
|
66
|
+
throw new AuthError(
|
|
67
|
+
'concurrent_change',
|
|
68
|
+
'The legacy terminal layout migration changed concurrently.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const remaining = migrateV24LegacyTerminalLayoutDocument(
|
|
72
|
+
persistedBodyFromRawDocument(concurrent),
|
|
73
|
+
);
|
|
74
|
+
assertProtocolV6NormalizableLayout(remaining.document);
|
|
75
|
+
if (remaining.migrated) {
|
|
76
|
+
throw new AuthError(
|
|
77
|
+
'concurrent_change',
|
|
78
|
+
'The legacy terminal layout migration changed concurrently.',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const last = documents.at(-1);
|
|
84
|
+
if (!last || documents.length < migrationPageLimit) break;
|
|
85
|
+
lastId = last._id;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|