agl 22.0.0 → 22.0.2
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 +20 -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.cli.d.ts +4 -2
- package/dist_ts/classes.cli.js +161 -68
- package/dist_ts/classes.controller.js +68 -30
- package/dist_ts/classes.upgradecoordinator.d.ts +39 -1
- package/dist_ts/classes.upgradecoordinator.js +740 -74
- package/dist_ts/classes.upgradetransaction.d.ts +68 -1
- package/dist_ts/classes.upgradetransaction.js +383 -223
- 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/readme.md +255 -378
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.authstore.ts +0 -15
- package/ts/classes.cli.ts +184 -83
- package/ts/classes.controller.ts +79 -33
- package/ts/classes.upgradecoordinator.ts +876 -74
- package/ts/classes.upgradetransaction.ts +574 -250
- 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
|
|
|
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
|
+
}
|
|
@@ -796,6 +796,9 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
796
796
|
@plugins.deesElement.state()
|
|
797
797
|
accessor workspaceError = '';
|
|
798
798
|
|
|
799
|
+
@plugins.deesElement.state()
|
|
800
|
+
accessor sessionLayoutError = '';
|
|
801
|
+
|
|
799
802
|
@plugins.deesElement.state()
|
|
800
803
|
accessor workspaceNotice = '';
|
|
801
804
|
|
|
@@ -1403,6 +1406,11 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
1403
1406
|
line-height: 1.4;
|
|
1404
1407
|
}
|
|
1405
1408
|
|
|
1409
|
+
.sessionLayoutError {
|
|
1410
|
+
flex: 0 0 auto;
|
|
1411
|
+
margin: 0 var(--dees-spacing-sm) var(--dees-spacing-xs);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1406
1414
|
.sidebarHeader {
|
|
1407
1415
|
display: flex;
|
|
1408
1416
|
align-items: center;
|
|
@@ -2042,6 +2050,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2042
2050
|
this.draftSessionActive = false;
|
|
2043
2051
|
this.activeMutationId = undefined;
|
|
2044
2052
|
this.workspaceError = '';
|
|
2053
|
+
this.sessionLayoutError = '';
|
|
2045
2054
|
this.workspaceNotice = '';
|
|
2046
2055
|
this.clearAnsweredCardCaches();
|
|
2047
2056
|
this.clearSubagentModalState();
|
|
@@ -2565,6 +2574,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
2565
2574
|
this.invalidateDetailLoad();
|
|
2566
2575
|
this.invalidateSlashCatalog();
|
|
2567
2576
|
this.sessionDetail = undefined;
|
|
2577
|
+
this.sessionLayoutError = '';
|
|
2568
2578
|
}
|
|
2569
2579
|
} catch (error) {
|
|
2570
2580
|
if (requestId === this.projectsRequestId && this.isCurrentConnection(generation)) {
|
|
@@ -7241,8 +7251,17 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
7241
7251
|
this.confirmedSessionLayouts.set(projectId, layout);
|
|
7242
7252
|
this.applySessionLayout(layout);
|
|
7243
7253
|
this.groupsLoadedForProjectId = projectId;
|
|
7244
|
-
|
|
7245
|
-
|
|
7254
|
+
this.sessionLayoutError = '';
|
|
7255
|
+
} catch (error) {
|
|
7256
|
+
if (
|
|
7257
|
+
requestId === this.layoutRequestId
|
|
7258
|
+
&& this.authenticated
|
|
7259
|
+
&& this.isCurrentConnection(generation)
|
|
7260
|
+
&& this.selectedProjectId === projectId
|
|
7261
|
+
&& this.projects.some((project) => project.id === projectId)
|
|
7262
|
+
) {
|
|
7263
|
+
this.sessionLayoutError = `Conversation groups could not be loaded: ${errorMessage(error)}`;
|
|
7264
|
+
}
|
|
7246
7265
|
}
|
|
7247
7266
|
}
|
|
7248
7267
|
|
|
@@ -8183,6 +8202,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
8183
8202
|
this.sessionGroups = [];
|
|
8184
8203
|
this.ungroupedSessionIds = [];
|
|
8185
8204
|
this.groupsLoadedForProjectId = '';
|
|
8205
|
+
this.sessionLayoutError = '';
|
|
8186
8206
|
this.terminals = [];
|
|
8187
8207
|
this.resources = [];
|
|
8188
8208
|
this.selectedResourceId = '';
|
|
@@ -8264,6 +8284,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
8264
8284
|
this.draftSessionDetailReadyId = undefined;
|
|
8265
8285
|
this.draftSessionActive = false;
|
|
8266
8286
|
this.selectedProjectId = '';
|
|
8287
|
+
this.sessionLayoutError = '';
|
|
8267
8288
|
this.selectedSessionId = undefined;
|
|
8268
8289
|
this.invalidateDetailLoad();
|
|
8269
8290
|
this.invalidateSlashCatalog();
|
|
@@ -8372,6 +8393,7 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
8372
8393
|
this.draftSessionDetailReadyId = undefined;
|
|
8373
8394
|
this.draftSessionActive = false;
|
|
8374
8395
|
this.selectedProjectId = response.project.id;
|
|
8396
|
+
this.sessionLayoutError = '';
|
|
8375
8397
|
this.selectedSessionId = undefined;
|
|
8376
8398
|
this.invalidateDetailLoad();
|
|
8377
8399
|
this.invalidateSlashCatalog();
|
|
@@ -10545,6 +10567,14 @@ export class HarnessControllerApp extends plugins.deesElement.DeesElement {
|
|
|
10545
10567
|
New
|
|
10546
10568
|
</dees-button>
|
|
10547
10569
|
</div>
|
|
10570
|
+
${this.sessionLayoutError
|
|
10571
|
+
? plugins.deesElement.html`
|
|
10572
|
+
<div class="projectCreateError sessionLayoutError" role="alert">
|
|
10573
|
+
<dees-icon icon="lucide:TriangleAlert" iconSize="13"></dees-icon>
|
|
10574
|
+
<span>${this.sessionLayoutError}</span>
|
|
10575
|
+
</div>
|
|
10576
|
+
`
|
|
10577
|
+
: ''}
|
|
10548
10578
|
<dees-harness-session-list
|
|
10549
10579
|
.sessions=${sessionMetas}
|
|
10550
10580
|
.selectedSessionId=${selectedUiKey}
|