agl 1.0.17 → 21.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.
Files changed (277) hide show
  1. package/.smartconfig.json +55 -0
  2. package/changelog.md +1026 -0
  3. package/cli.js +14 -0
  4. package/dist_serve/bundle.js +44778 -0
  5. package/dist_serve/index.html +13 -0
  6. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  7. package/dist_ts/00_commitinfo_data.js +9 -0
  8. package/dist_ts/classes.authmodels.d.ts +126 -0
  9. package/dist_ts/classes.authmodels.js +1422 -0
  10. package/dist_ts/classes.authstore.d.ts +160 -0
  11. package/dist_ts/classes.authstore.js +2483 -0
  12. package/dist_ts/classes.browserresourcehost.d.ts +85 -0
  13. package/dist_ts/classes.browserresourcehost.js +912 -0
  14. package/dist_ts/classes.cli.d.ts +21 -0
  15. package/dist_ts/classes.cli.js +1082 -0
  16. package/dist_ts/classes.config.d.ts +23 -0
  17. package/dist_ts/classes.config.js +267 -0
  18. package/dist_ts/classes.controller.d.ts +523 -0
  19. package/dist_ts/classes.controller.js +10611 -0
  20. package/dist_ts/classes.controllermanagement.d.ts +32 -0
  21. package/dist_ts/classes.controllermanagement.js +222 -0
  22. package/dist_ts/classes.draftmanager.d.ts +30 -0
  23. package/dist_ts/classes.draftmanager.js +179 -0
  24. package/dist_ts/classes.embeddeddb.d.ts +30 -0
  25. package/dist_ts/classes.embeddeddb.js +159 -0
  26. package/dist_ts/classes.flexframedtransport.d.ts +56 -0
  27. package/dist_ts/classes.flexframedtransport.js +275 -0
  28. package/dist_ts/classes.flexmodels.private.d.ts +231 -0
  29. package/dist_ts/classes.flexmodels.private.js +985 -0
  30. package/dist_ts/classes.flexmodels.public.d.ts +207 -0
  31. package/dist_ts/classes.flexmodels.public.js +885 -0
  32. package/dist_ts/classes.flexprojectionreader.d.ts +89 -0
  33. package/dist_ts/classes.flexprojectionreader.js +580 -0
  34. package/dist_ts/classes.flexservice.d.ts +196 -0
  35. package/dist_ts/classes.flexservice.js +3704 -0
  36. package/dist_ts/classes.flexstore.d.ts +174 -0
  37. package/dist_ts/classes.flexstore.js +3400 -0
  38. package/dist_ts/classes.flexsupervisor.d.ts +105 -0
  39. package/dist_ts/classes.flexsupervisor.js +1191 -0
  40. package/dist_ts/classes.gitreversion.d.ts +431 -0
  41. package/dist_ts/classes.gitreversion.js +10592 -0
  42. package/dist_ts/classes.harnessoperationmanager.d.ts +113 -0
  43. package/dist_ts/classes.harnessoperationmanager.js +602 -0
  44. package/dist_ts/classes.opencodeclient.d.ts +146 -0
  45. package/dist_ts/classes.opencodeclient.js +3003 -0
  46. package/dist_ts/classes.opencodesupervisor.d.ts +60 -0
  47. package/dist_ts/classes.opencodesupervisor.js +1003 -0
  48. package/dist_ts/classes.passkeymanager.d.ts +34 -0
  49. package/dist_ts/classes.passkeymanager.js +456 -0
  50. package/dist_ts/classes.processinspection.d.ts +50 -0
  51. package/dist_ts/classes.processinspection.js +484 -0
  52. package/dist_ts/classes.resourcecoordinator.d.ts +64 -0
  53. package/dist_ts/classes.resourcecoordinator.js +294 -0
  54. package/dist_ts/classes.sessionintelligencereader.d.ts +13 -0
  55. package/dist_ts/classes.sessionintelligencereader.js +40 -0
  56. package/dist_ts/classes.systemmetricssampler.d.ts +53 -0
  57. package/dist_ts/classes.systemmetricssampler.js +168 -0
  58. package/dist_ts/classes.terminalmanager.d.ts +95 -0
  59. package/dist_ts/classes.terminalmanager.js +981 -0
  60. package/dist_ts/classes.terminalresourcehost.d.ts +20 -0
  61. package/dist_ts/classes.terminalresourcehost.js +84 -0
  62. package/dist_ts/classes.upgradecoordinator.d.ts +244 -0
  63. package/dist_ts/classes.upgradecoordinator.js +2155 -0
  64. package/dist_ts/classes.upgradetransaction.d.ts +92 -0
  65. package/dist_ts/classes.upgradetransaction.js +2125 -0
  66. package/dist_ts/classes.uploadmanager.d.ts +24 -0
  67. package/dist_ts/classes.uploadmanager.js +210 -0
  68. package/dist_ts/classes.validation.d.ts +91 -0
  69. package/dist_ts/classes.validation.js +1087 -0
  70. package/dist_ts/flexharness.child.d.ts +1 -0
  71. package/dist_ts/flexharness.child.js +28 -0
  72. package/dist_ts/functions.canonicaldirectory.d.ts +21 -0
  73. package/dist_ts/functions.canonicaldirectory.js +144 -0
  74. package/dist_ts/functions.controllerdataroot.d.ts +15 -0
  75. package/dist_ts/functions.controllerdataroot.js +76 -0
  76. package/dist_ts/functions.embeddeddb.d.ts +2 -0
  77. package/dist_ts/functions.embeddeddb.js +21 -0
  78. package/dist_ts/functions.flexcredentialstore.d.ts +16 -0
  79. package/dist_ts/functions.flexcredentialstore.js +93 -0
  80. package/dist_ts/functions.opencodeauth.d.ts +3 -0
  81. package/dist_ts/functions.opencodeauth.js +31 -0
  82. package/dist_ts/functions.runtimeenvironment.d.ts +2 -0
  83. package/dist_ts/functions.runtimeenvironment.js +79 -0
  84. package/dist_ts/functions.upgradefollower.d.ts +3 -0
  85. package/dist_ts/functions.upgradefollower.js +95 -0
  86. package/dist_ts/index.d.ts +24 -0
  87. package/dist_ts/index.js +25 -0
  88. package/dist_ts/interfaces.auth.d.ts +181 -0
  89. package/dist_ts/interfaces.auth.js +82 -0
  90. package/dist_ts/interfaces.config.d.ts +34 -0
  91. package/dist_ts/interfaces.config.js +2 -0
  92. package/dist_ts/interfaces.flexipc.d.ts +939 -0
  93. package/dist_ts/interfaces.flexipc.js +1492 -0
  94. package/dist_ts/interfaces.gitreversion.d.ts +246 -0
  95. package/dist_ts/interfaces.gitreversion.js +2 -0
  96. package/dist_ts/interfaces.lifecycle.d.ts +37 -0
  97. package/dist_ts/interfaces.lifecycle.js +2 -0
  98. package/dist_ts/interfaces.opencode.d.ts +101 -0
  99. package/dist_ts/interfaces.opencode.js +3 -0
  100. package/dist_ts/interfaces.projects.d.ts +156 -0
  101. package/dist_ts/interfaces.projects.js +5 -0
  102. package/dist_ts/plugins.d.ts +36 -0
  103. package/dist_ts/plugins.js +40 -0
  104. package/dist_ts_interfaces/index.d.ts +2 -0
  105. package/dist_ts_interfaces/index.js +3 -0
  106. package/dist_ts_interfaces/interfaces.d.ts +747 -0
  107. package/dist_ts_interfaces/interfaces.js +47 -0
  108. package/dist_ts_interfaces/plugins.d.ts +4 -0
  109. package/dist_ts_interfaces/plugins.js +3 -0
  110. package/dist_ts_interfaces/requests.d.ts +806 -0
  111. package/dist_ts_interfaces/requests.js +2 -0
  112. package/dist_ts_migration/classes.documentmigrationrunner.d.ts +8 -0
  113. package/dist_ts_migration/classes.documentmigrationrunner.js +24 -0
  114. package/dist_ts_migration/classes.flexmigrationrunner.d.ts +11 -0
  115. package/dist_ts_migration/classes.flexmigrationrunner.js +25 -0
  116. package/dist_ts_migration/classes.flexpostharnessmigrationrunner.d.ts +10 -0
  117. package/dist_ts_migration/classes.flexpostharnessmigrationrunner.js +15 -0
  118. package/dist_ts_migration/classes.migrationrunner.d.ts +8 -0
  119. package/dist_ts_migration/classes.migrationrunner.js +34 -0
  120. package/dist_ts_migration/index.d.ts +12 -0
  121. package/dist_ts_migration/index.js +13 -0
  122. package/dist_ts_migration/v10_flexharnessv3migration.d.ts +11 -0
  123. package/dist_ts_migration/v10_flexharnessv3migration.js +23 -0
  124. package/dist_ts_migration/v13_flexprojectionorder.d.ts +11 -0
  125. package/dist_ts_migration/v13_flexprojectionorder.js +23 -0
  126. package/dist_ts_migration/v16_flexprovidercredentials.d.ts +22 -0
  127. package/dist_ts_migration/v16_flexprovidercredentials.js +149 -0
  128. package/dist_ts_migration/v18_resourcelayout.d.ts +22 -0
  129. package/dist_ts_migration/v18_resourcelayout.js +23 -0
  130. package/dist_ts_migration/v18_resourcelayoutmigration.d.ts +6 -0
  131. package/dist_ts_migration/v18_resourcelayoutmigration.js +61 -0
  132. package/dist_ts_migration/v19_flexprojectmanagement.d.ts +11 -0
  133. package/dist_ts_migration/v19_flexprojectmanagement.js +21 -0
  134. package/dist_ts_migration/v1_embeddeddatabaselocation.d.ts +28 -0
  135. package/dist_ts_migration/v1_embeddeddatabaselocation.js +415 -0
  136. package/dist_ts_migration/v22_projectfilesystemidentitymigration.d.ts +7 -0
  137. package/dist_ts_migration/v22_projectfilesystemidentitymigration.js +105 -0
  138. package/dist_ts_migration/v2_controllerdataroot.d.ts +130 -0
  139. package/dist_ts_migration/v2_controllerdataroot.js +1887 -0
  140. package/dist_ts_migration/v6_protocoldocumentmigration.d.ts +7 -0
  141. package/dist_ts_migration/v6_protocoldocumentmigration.js +100 -0
  142. package/dist_ts_migration/v6_protocoldocuments.d.ts +7 -0
  143. package/dist_ts_migration/v6_protocoldocuments.js +138 -0
  144. package/html/index.html +13 -0
  145. package/{LICENSE → license.md} +2 -2
  146. package/package.json +86 -48
  147. package/readme.md +480 -0
  148. package/readme.plan.md +188 -0
  149. package/ts/00_commitinfo_data.ts +8 -0
  150. package/ts/classes.authmodels.ts +1245 -0
  151. package/ts/classes.authstore.ts +3274 -0
  152. package/ts/classes.browserresourcehost.ts +1205 -0
  153. package/ts/classes.cli.ts +1254 -0
  154. package/ts/classes.config.ts +351 -0
  155. package/ts/classes.controller.ts +14426 -0
  156. package/ts/classes.controllermanagement.ts +330 -0
  157. package/ts/classes.draftmanager.ts +232 -0
  158. package/ts/classes.embeddeddb.ts +166 -0
  159. package/ts/classes.flexframedtransport.ts +283 -0
  160. package/ts/classes.flexmodels.private.ts +823 -0
  161. package/ts/classes.flexmodels.public.ts +732 -0
  162. package/ts/classes.flexprojectionreader.ts +816 -0
  163. package/ts/classes.flexservice.ts +4302 -0
  164. package/ts/classes.flexstore.ts +4565 -0
  165. package/ts/classes.flexsupervisor.ts +1453 -0
  166. package/ts/classes.gitreversion.ts +13353 -0
  167. package/ts/classes.harnessoperationmanager.ts +870 -0
  168. package/ts/classes.opencodeclient.ts +4110 -0
  169. package/ts/classes.opencodesupervisor.ts +1280 -0
  170. package/ts/classes.passkeymanager.ts +570 -0
  171. package/ts/classes.processinspection.ts +623 -0
  172. package/ts/classes.resourcecoordinator.ts +436 -0
  173. package/ts/classes.sessionintelligencereader.ts +52 -0
  174. package/ts/classes.systemmetricssampler.ts +215 -0
  175. package/ts/classes.terminalmanager.ts +1207 -0
  176. package/ts/classes.terminalresourcehost.ts +122 -0
  177. package/ts/classes.upgradecoordinator.ts +2675 -0
  178. package/ts/classes.upgradetransaction.ts +2900 -0
  179. package/ts/classes.uploadmanager.ts +240 -0
  180. package/ts/classes.validation.ts +1409 -0
  181. package/ts/flexharness.child.ts +35 -0
  182. package/ts/functions.canonicaldirectory.ts +213 -0
  183. package/ts/functions.controllerdataroot.ts +107 -0
  184. package/ts/functions.embeddeddb.ts +22 -0
  185. package/ts/functions.flexcredentialstore.ts +128 -0
  186. package/ts/functions.opencodeauth.ts +34 -0
  187. package/ts/functions.runtimeenvironment.ts +90 -0
  188. package/ts/functions.upgradefollower.ts +115 -0
  189. package/ts/index.ts +36 -6
  190. package/ts/interfaces.auth.ts +316 -0
  191. package/ts/interfaces.config.ts +37 -0
  192. package/ts/interfaces.flexipc.ts +2586 -0
  193. package/ts/interfaces.gitreversion.ts +336 -0
  194. package/ts/interfaces.lifecycle.ts +46 -0
  195. package/ts/interfaces.opencode.ts +153 -0
  196. package/ts/interfaces.projects.ts +182 -0
  197. package/ts/plugins.ts +64 -0
  198. package/ts_interfaces/index.ts +2 -0
  199. package/ts_interfaces/interfaces.ts +827 -0
  200. package/ts_interfaces/plugins.ts +15 -0
  201. package/ts_interfaces/requests.ts +1186 -0
  202. package/ts_migration/classes.documentmigrationrunner.ts +32 -0
  203. package/ts_migration/classes.flexmigrationrunner.ts +33 -0
  204. package/ts_migration/classes.flexpostharnessmigrationrunner.ts +22 -0
  205. package/ts_migration/classes.migrationrunner.ts +40 -0
  206. package/ts_migration/index.ts +12 -0
  207. package/ts_migration/v10_flexharnessv3migration.ts +35 -0
  208. package/ts_migration/v13_flexprojectionorder.ts +33 -0
  209. package/ts_migration/v16_flexprovidercredentials.ts +196 -0
  210. package/ts_migration/v18_resourcelayout.ts +55 -0
  211. package/ts_migration/v18_resourcelayoutmigration.ts +68 -0
  212. package/ts_migration/v19_flexprojectmanagement.ts +28 -0
  213. package/ts_migration/v1_embeddeddatabaselocation.ts +560 -0
  214. package/ts_migration/v22_projectfilesystemidentitymigration.ts +121 -0
  215. package/ts_migration/v2_controllerdataroot.ts +2349 -0
  216. package/ts_migration/v6_protocoldocumentmigration.ts +144 -0
  217. package/ts_migration/v6_protocoldocuments.ts +198 -0
  218. package/ts_web/00_commitinfo_data.ts +8 -0
  219. package/ts_web/classes.controllersocketclient.ts +517 -0
  220. package/ts_web/classes.sessiondraftsync.ts +390 -0
  221. package/ts_web/elements.harnesscontrollerapp.ts +11494 -0
  222. package/ts_web/index.ts +2 -0
  223. package/ts_web/plugins.ts +31 -0
  224. package/.gitlab-ci.yml +0 -71
  225. package/.npmignore +0 -5
  226. package/README.md +0 -40
  227. package/dist/cli.js +0 -2
  228. package/dist/index.d.ts +0 -0
  229. package/dist/index.js +0 -9
  230. package/dist/mod_bundle/index.d.ts +0 -1
  231. package/dist/mod_bundle/index.js +0 -18
  232. package/dist/mod_bundle/mod.plugins.d.ts +0 -4
  233. package/dist/mod_bundle/mod.plugins.js +0 -11
  234. package/dist/mod_install/index.d.ts +0 -4
  235. package/dist/mod_install/index.js +0 -21
  236. package/dist/mod_install/mod.plugins.d.ts +0 -1
  237. package/dist/mod_install/mod.plugins.js +0 -7
  238. package/dist/mod_standard/index.d.ts +0 -4
  239. package/dist/mod_standard/index.js +0 -21
  240. package/dist/mod_standard/mod.browser.d.ts +0 -6
  241. package/dist/mod_standard/mod.browser.js +0 -14
  242. package/dist/mod_standard/mod.clean.d.ts +0 -4
  243. package/dist/mod_standard/mod.clean.js +0 -12
  244. package/dist/mod_standard/mod.compile.d.ts +0 -4
  245. package/dist/mod_standard/mod.compile.js +0 -21
  246. package/dist/mod_standard/mod.plugins.d.ts +0 -8
  247. package/dist/mod_standard/mod.plugins.js +0 -19
  248. package/dist/mod_standard/mod.test.d.ts +0 -4
  249. package/dist/mod_standard/mod.test.js +0 -12
  250. package/dist/plr.cli.d.ts +0 -3
  251. package/dist/plr.cli.js +0 -57
  252. package/dist/plr.config.d.ts +0 -11
  253. package/dist/plr.config.js +0 -26
  254. package/dist/plr.mods.d.ts +0 -17
  255. package/dist/plr.mods.js +0 -17
  256. package/dist/plr.paths.d.ts +0 -19
  257. package/dist/plr.paths.js +0 -26
  258. package/dist/plr.plugins.d.ts +0 -8
  259. package/dist/plr.plugins.js +0 -16
  260. package/npmextra.json +0 -10
  261. package/ts/mod_bundle/index.ts +0 -20
  262. package/ts/mod_bundle/mod.plugins.ts +0 -9
  263. package/ts/mod_install/index.ts +0 -20
  264. package/ts/mod_install/mod.plugins.ts +0 -1
  265. package/ts/mod_standard/index.ts +0 -23
  266. package/ts/mod_standard/mod.browser.ts +0 -17
  267. package/ts/mod_standard/mod.clean.ts +0 -13
  268. package/ts/mod_standard/mod.compile.ts +0 -23
  269. package/ts/mod_standard/mod.plugins.ts +0 -17
  270. package/ts/mod_standard/mod.test.ts +0 -13
  271. package/ts/plr.cli.ts +0 -60
  272. package/ts/plr.config.ts +0 -36
  273. package/ts/plr.mods.ts +0 -25
  274. package/ts/plr.paths.ts +0 -29
  275. package/ts/plr.plugins.ts +0 -17
  276. package/tslint.json +0 -3
  277. package/yarn.lock +0 -3464
@@ -0,0 +1,330 @@
1
+ import * as plugins from './plugins.js';
2
+ import {
3
+ controllerPackageName,
4
+ type IControllerStatus,
5
+ type IReq_ControllerStatus,
6
+ type IReq_ControllerUpgradeLaunch,
7
+ type IReq_ControllerUpgradePrepareBegin,
8
+ type IReq_ControllerUpgradeFinalize,
9
+ } from '../ts_interfaces/index.js';
10
+ import {
11
+ inspectControllerProcess,
12
+ signalVerifiedControllerProcess,
13
+ } from './classes.processinspection.js';
14
+
15
+ const currentCliName = String(controllerPackageName) === 'agl' ? 'agl' : 'hcon';
16
+ const currentRuntimeName = currentCliName === 'agl' ? 'AGL' : currentCliName;
17
+
18
+ export interface IControllerStatusExpectation {
19
+ packageName: string;
20
+ packageVersion: string;
21
+ protocolVersion?: number;
22
+ upgradeManagementVersion: number;
23
+ }
24
+
25
+ const harnessStates = ['starting', 'ready', 'stopping', 'stopped', 'failed'] as const;
26
+ export const controllerUpgradePrepareBeginRequestTimeoutMs = 30_000;
27
+
28
+ const isControllerHarnessStatus = (statusArg: unknown): boolean => {
29
+ if (typeof statusArg !== 'object' || statusArg === null) return false;
30
+ const status = statusArg as Record<string, unknown>;
31
+ return (
32
+ (status.harnessId === 'opencode' || status.harnessId === 'flex')
33
+ && harnessStates.some((stateArg) => status.state === stateArg)
34
+ && typeof status.healthy === 'boolean'
35
+ && (status.pid === undefined || Number.isSafeInteger(status.pid))
36
+ && (status.version === undefined || typeof status.version === 'string')
37
+ );
38
+ };
39
+
40
+ const isControllerUpgradeStatus = (statusArg: unknown): boolean => {
41
+ if (!statusArg || typeof statusArg !== 'object' || Array.isArray(statusArg)) return false;
42
+ const status = statusArg as Record<string, unknown>;
43
+ return Object.keys(status).length === 3
44
+ && typeof status.fromVersion === 'string'
45
+ && status.fromVersion.length > 0
46
+ && Buffer.byteLength(status.fromVersion, 'utf8') <= 128
47
+ && typeof status.toVersion === 'string'
48
+ && status.toVersion.length > 0
49
+ && Buffer.byteLength(status.toVersion, 'utf8') <= 128
50
+ && typeof status.phase === 'string'
51
+ && ['preparing', 'pausing', 'installing', 'restarting', 'continuing', 'completed', 'failed']
52
+ .includes(status.phase);
53
+ };
54
+
55
+ export const assertControllerStatus = (
56
+ statusArg: unknown,
57
+ expectationArg: IControllerStatusExpectation,
58
+ ): IControllerStatus => {
59
+ if (typeof statusArg !== 'object' || statusArg === null) {
60
+ throw new Error(`The service on the management port is not an ${currentRuntimeName} endpoint.`);
61
+ }
62
+ const status = statusArg as Partial<IControllerStatus>;
63
+ const harnesses = Array.isArray(status.harnesses) ? status.harnesses : [];
64
+ const openCode = harnesses.find((entryArg) => entryArg?.harnessId === 'opencode');
65
+ const flex = harnesses.find((entryArg) => entryArg?.harnessId === 'flex');
66
+ if (
67
+ status.packageName !== expectationArg.packageName
68
+ || status.packageVersion !== expectationArg.packageVersion
69
+ || (expectationArg.protocolVersion !== undefined
70
+ && status.protocolVersion !== expectationArg.protocolVersion)
71
+ || status.upgradeManagementVersion !== expectationArg.upgradeManagementVersion
72
+ || !Number.isSafeInteger(status.controllerPid)
73
+ || !Number.isSafeInteger(status.processGroupId)
74
+ || typeof status.processFingerprint !== 'string'
75
+ || status.processFingerprint.length < 8
76
+ || status.processFingerprint.length > 256
77
+ || (status.processMode !== 'detached' && status.processMode !== 'foreground')
78
+ || (status.lifecycleState !== 'starting'
79
+ && status.lifecycleState !== 'ready'
80
+ && status.lifecycleState !== 'stopping'
81
+ && status.lifecycleState !== 'stopped')
82
+ || typeof status.startedAt !== 'number'
83
+ || typeof status.setupRequired !== 'boolean'
84
+ || (status.upgrade !== undefined && !isControllerUpgradeStatus(status.upgrade))
85
+ || harnesses.length !== 2
86
+ || !openCode
87
+ || !flex
88
+ || !harnesses.every((entryArg) => isControllerHarnessStatus(entryArg))
89
+ ) {
90
+ throw new Error('The service on the management port has an incompatible controller identity.');
91
+ }
92
+ return status as IControllerStatus;
93
+ };
94
+
95
+ export const requestControllerUpgradePrepareBegin = async (
96
+ portArg: number,
97
+ tokenArg: string,
98
+ targetVersionArg: string,
99
+ gracePeriodMsArg: number,
100
+ ): Promise<IReq_ControllerUpgradePrepareBegin['response']> => {
101
+ const abortController = new AbortController();
102
+ const timeout = setTimeout(
103
+ () => abortController.abort(),
104
+ controllerUpgradePrepareBeginRequestTimeoutMs,
105
+ );
106
+ let socket: plugins.typedsocket.TypedSocket | undefined;
107
+ try {
108
+ socket = await plugins.typedsocket.TypedSocket.createClient(
109
+ new plugins.typedrequest.TypedRouter(),
110
+ `http://127.0.0.1:${portArg}`,
111
+ {
112
+ autoReconnect: false,
113
+ maxRetries: 0,
114
+ abortSignal: abortController.signal,
115
+ },
116
+ );
117
+ const response = await socket.createTypedRequest<IReq_ControllerUpgradePrepareBegin>(
118
+ 'controller.upgrade.prepare.begin',
119
+ undefined,
120
+ {
121
+ timeoutMs: controllerUpgradePrepareBeginRequestTimeoutMs,
122
+ abortSignal: abortController.signal,
123
+ },
124
+ ).fire({ token: tokenArg, targetVersion: targetVersionArg, gracePeriodMs: gracePeriodMsArg });
125
+ if (
126
+ !response
127
+ || typeof response !== 'object'
128
+ || Array.isArray(response)
129
+ || Object.keys(response).length !== 1
130
+ || response.accepted !== true
131
+ ) throw new Error('The controller returned an invalid upgrade preparation response.');
132
+ return response;
133
+ } finally {
134
+ clearTimeout(timeout);
135
+ await socket?.stop();
136
+ }
137
+ };
138
+
139
+ export const requestControllerUpgradeFinalize = async (
140
+ portArg: number,
141
+ tokenArg: string,
142
+ modeArg: IReq_ControllerUpgradeFinalize['request']['mode'],
143
+ timeoutMsArg = 60_000,
144
+ ): Promise<IReq_ControllerUpgradeFinalize['response']> => {
145
+ const socket = await plugins.typedsocket.TypedSocket.createClient(
146
+ new plugins.typedrequest.TypedRouter(),
147
+ `http://127.0.0.1:${portArg}`,
148
+ { autoReconnect: false, maxRetries: 0 },
149
+ );
150
+ try {
151
+ return await socket.createTypedRequest<IReq_ControllerUpgradeFinalize>(
152
+ 'controller.upgrade.finalize',
153
+ undefined,
154
+ { timeoutMs: timeoutMsArg },
155
+ ).fire({ token: tokenArg, mode: modeArg });
156
+ } finally {
157
+ await socket.stop();
158
+ }
159
+ };
160
+
161
+ export const queryControllerStatus = async (
162
+ portArg: number,
163
+ expectationArg: IControllerStatusExpectation,
164
+ timeoutMsArg = 2_500,
165
+ ): Promise<IControllerStatus> => {
166
+ const abortController = new AbortController();
167
+ const timeout = setTimeout(() => abortController.abort(), timeoutMsArg);
168
+ let socket: plugins.typedsocket.TypedSocket | undefined;
169
+ try {
170
+ const router = new plugins.typedrequest.TypedRouter();
171
+ socket = await plugins.typedsocket.TypedSocket.createClient(
172
+ router,
173
+ `http://127.0.0.1:${portArg}`,
174
+ {
175
+ autoReconnect: false,
176
+ maxRetries: 0,
177
+ abortSignal: abortController.signal,
178
+ },
179
+ );
180
+ const request = socket.createTypedRequest<IReq_ControllerStatus>(
181
+ 'controller.status',
182
+ undefined,
183
+ { timeoutMs: timeoutMsArg, abortSignal: abortController.signal },
184
+ );
185
+ return assertControllerStatus(await request.fire({}), expectationArg);
186
+ } finally {
187
+ clearTimeout(timeout);
188
+ await socket?.stop();
189
+ }
190
+ };
191
+
192
+ export const tryQueryControllerStatus = async (
193
+ portArg: number,
194
+ expectationArg: IControllerStatusExpectation,
195
+ ): Promise<IControllerStatus | undefined> => {
196
+ try {
197
+ return await queryControllerStatus(portArg, expectationArg);
198
+ } catch {
199
+ return undefined;
200
+ }
201
+ };
202
+
203
+ export const isLoopbackPortListening = async (
204
+ portArg: number,
205
+ timeoutMsArg = 500,
206
+ ): Promise<boolean> => {
207
+ return await new Promise<boolean>((resolve) => {
208
+ const socket = plugins.net.createConnection({ host: '127.0.0.1', port: portArg });
209
+ let settled = false;
210
+ const finish = (resultArg: boolean) => {
211
+ if (settled) return;
212
+ settled = true;
213
+ clearTimeout(timeout);
214
+ socket.destroy();
215
+ resolve(resultArg);
216
+ };
217
+ const timeout = setTimeout(() => finish(false), timeoutMsArg);
218
+ socket.once('connect', () => finish(true));
219
+ socket.once('error', () => finish(false));
220
+ });
221
+ };
222
+
223
+ export const requestControllerUpgradeLaunch = async (
224
+ portArg: number,
225
+ tokenArg: string,
226
+ timeoutMsArg = 15_000,
227
+ ): Promise<IReq_ControllerUpgradeLaunch['response']> => {
228
+ const abortController = new AbortController();
229
+ const timeout = setTimeout(() => abortController.abort(), timeoutMsArg);
230
+ let socket: plugins.typedsocket.TypedSocket | undefined;
231
+ try {
232
+ const router = new plugins.typedrequest.TypedRouter();
233
+ socket = await plugins.typedsocket.TypedSocket.createClient(
234
+ router,
235
+ `http://127.0.0.1:${portArg}`,
236
+ {
237
+ autoReconnect: false,
238
+ maxRetries: 0,
239
+ abortSignal: abortController.signal,
240
+ },
241
+ );
242
+ const response = await socket.createTypedRequest<IReq_ControllerUpgradeLaunch>(
243
+ 'controller.upgrade.launch',
244
+ undefined,
245
+ { timeoutMs: timeoutMsArg, abortSignal: abortController.signal },
246
+ ).fire({ token: tokenArg });
247
+ if (
248
+ !response
249
+ || typeof response !== 'object'
250
+ || response.accepted !== true
251
+ || !Number.isSafeInteger(response.workerPid)
252
+ || response.workerPid < 2
253
+ || typeof response.logFilePath !== 'string'
254
+ || !plugins.path.isAbsolute(response.logFilePath)
255
+ ) {
256
+ throw new Error(`The controller returned an invalid ${currentCliName} upgrade launch response.`);
257
+ }
258
+ return response;
259
+ } finally {
260
+ clearTimeout(timeout);
261
+ await socket?.stop();
262
+ }
263
+ };
264
+
265
+ export const waitUntilControllerExited = async (
266
+ optionsArg: {
267
+ pid: number;
268
+ cliPath: string;
269
+ port: number;
270
+ expectedProcessGroupId: number;
271
+ expectedFingerprint: string;
272
+ command: '__serve' | 'foreground';
273
+ },
274
+ timeoutMsArg: number,
275
+ ): Promise<boolean> => {
276
+ const deadline = Date.now() + timeoutMsArg;
277
+ while (Date.now() < deadline) {
278
+ if (!await inspectControllerProcess(optionsArg)) return true;
279
+ await new Promise((resolve) => setTimeout(resolve, 150));
280
+ }
281
+ return !await inspectControllerProcess(optionsArg);
282
+ };
283
+
284
+ export const resolveControllerStopSignal = (
285
+ statusArg: IControllerStatus,
286
+ phaseArg: 'cooperative' | 'hard',
287
+ ): { signal: NodeJS.Signals; signalProcessGroup: boolean } => {
288
+ if (phaseArg === 'cooperative') {
289
+ return { signal: 'SIGTERM', signalProcessGroup: false };
290
+ }
291
+ const signalProcessGroup = statusArg.processMode === 'detached';
292
+ if (signalProcessGroup && statusArg.processGroupId !== statusArg.controllerPid) {
293
+ throw new Error('The detached controller is not the verified leader of its process group.');
294
+ }
295
+ return { signal: 'SIGKILL', signalProcessGroup };
296
+ };
297
+
298
+ export const stopVerifiedController = async (optionsArg: {
299
+ status: IControllerStatus;
300
+ cliPath: string;
301
+ port: number;
302
+ }): Promise<void> => {
303
+ const command: '__serve' | 'foreground' = optionsArg.status.processMode === 'detached'
304
+ ? '__serve'
305
+ : 'foreground';
306
+ const processIdentity = {
307
+ pid: optionsArg.status.controllerPid,
308
+ cliPath: optionsArg.cliPath,
309
+ port: optionsArg.port,
310
+ expectedProcessGroupId: optionsArg.status.processGroupId,
311
+ expectedFingerprint: optionsArg.status.processFingerprint,
312
+ command,
313
+ };
314
+ const cooperativeSignal = resolveControllerStopSignal(optionsArg.status, 'cooperative');
315
+ await signalVerifiedControllerProcess({
316
+ ...processIdentity,
317
+ ...cooperativeSignal,
318
+ });
319
+ if (await waitUntilControllerExited(processIdentity, 30_000)) return;
320
+ const hardSignal = resolveControllerStopSignal(optionsArg.status, 'hard');
321
+ await signalVerifiedControllerProcess({
322
+ ...processIdentity,
323
+ ...hardSignal,
324
+ });
325
+ if (!await waitUntilControllerExited(processIdentity, 15_000)) {
326
+ throw new Error(
327
+ 'The exact controller process survived verified hard-stop escalation.',
328
+ );
329
+ }
330
+ };
@@ -0,0 +1,232 @@
1
+ import {
2
+ controllerMaxDraftAttachmentBytes,
3
+ controllerMaxDraftAttachmentTotalBytes,
4
+ controllerMaxDraftAttachments,
5
+ controllerMaxDraftTextBytes,
6
+ type IControllerDraftAttachment,
7
+ type IControllerSessionDraft,
8
+ type IControllerSessionDraftUpdate,
9
+ } from '../ts_interfaces/interfaces.js';
10
+
11
+ const maximumDraftIdentities = 4_096;
12
+ const maximumNonEmptyDrafts = 512;
13
+ const maximumAggregateAttachmentBytes = 64 * 1024 * 1024;
14
+
15
+ export class ControllerDraftConflictError extends Error {
16
+ constructor(public readonly draft: IControllerSessionDraft) {
17
+ super('The composer draft changed concurrently.');
18
+ this.name = 'ControllerDraftConflictError';
19
+ }
20
+ }
21
+
22
+ export class ControllerDraftLimitError extends Error {
23
+ constructor(messageArg: string) {
24
+ super(messageArg);
25
+ this.name = 'ControllerDraftLimitError';
26
+ }
27
+ }
28
+
29
+ export interface IControllerDraftMutationResult {
30
+ draft: IControllerSessionDraft;
31
+ update?: IControllerSessionDraftUpdate;
32
+ }
33
+
34
+ const cloneAttachment = (
35
+ attachmentArg: IControllerDraftAttachment,
36
+ ): IControllerDraftAttachment => ({ ...attachmentArg });
37
+
38
+ const cloneDraft = (draftArg: IControllerSessionDraft): IControllerSessionDraft => ({
39
+ text: draftArg.text,
40
+ attachments: draftArg.attachments.map(cloneAttachment),
41
+ revision: draftArg.revision,
42
+ });
43
+
44
+ const emptyDraft = (): IControllerSessionDraft => ({ text: '', attachments: [], revision: 0 });
45
+
46
+ const attachmentBytes = (draftArg: IControllerSessionDraft): number => (
47
+ draftArg.attachments.reduce((sum, attachment) => sum + attachment.size, 0)
48
+ );
49
+
50
+ const isNonEmpty = (draftArg: IControllerSessionDraft): boolean => (
51
+ draftArg.text.length > 0 || draftArg.attachments.length > 0
52
+ );
53
+
54
+ const attachmentsEqual = (
55
+ leftArg: readonly IControllerDraftAttachment[],
56
+ rightArg: readonly IControllerDraftAttachment[],
57
+ ): boolean => leftArg.length === rightArg.length && leftArg.every((left, index) => {
58
+ const right = rightArg[index];
59
+ return right !== undefined
60
+ && left.id === right.id
61
+ && left.name === right.name
62
+ && left.mediaType === right.mediaType
63
+ && left.size === right.size
64
+ && left.kind === right.kind
65
+ && left.dataBase64 === right.dataBase64;
66
+ });
67
+
68
+ export class ControllerDraftManager {
69
+ private readonly drafts = new Map<string, IControllerSessionDraft>();
70
+ private nonEmptyDrafts = 0;
71
+ private aggregateAttachmentBytes = 0;
72
+
73
+ public get(keyArg: string): IControllerSessionDraft {
74
+ return cloneDraft(this.drafts.get(keyArg) ?? emptyDraft());
75
+ }
76
+
77
+ public update(
78
+ keyArg: string,
79
+ expectedRevisionArg: number,
80
+ patchArg: { text?: string; attachments?: readonly IControllerDraftAttachment[] },
81
+ ): IControllerDraftMutationResult {
82
+ const current = this.drafts.get(keyArg) ?? emptyDraft();
83
+ if (current.revision !== expectedRevisionArg) {
84
+ throw new ControllerDraftConflictError(cloneDraft(current));
85
+ }
86
+ if (patchArg.text === undefined && patchArg.attachments === undefined) {
87
+ throw new Error('A draft update must replace text, attachments, or both.');
88
+ }
89
+
90
+ const nextText = patchArg.text ?? current.text;
91
+ const nextAttachments = patchArg.attachments === undefined
92
+ ? current.attachments
93
+ : patchArg.attachments.map(cloneAttachment);
94
+ this.assertDraftFields(nextText, nextAttachments);
95
+ const textChanged = nextText !== current.text;
96
+ const attachmentsChanged = !attachmentsEqual(nextAttachments, current.attachments);
97
+ if (!textChanged && !attachmentsChanged) return { draft: cloneDraft(current) };
98
+ if (current.revision >= Number.MAX_SAFE_INTEGER) {
99
+ throw new ControllerDraftLimitError('The draft revision limit was reached.');
100
+ }
101
+
102
+ const next: IControllerSessionDraft = {
103
+ text: nextText,
104
+ attachments: nextAttachments,
105
+ revision: current.revision + 1,
106
+ };
107
+ this.assertAdmission(keyArg, current, next);
108
+ this.store(keyArg, current, next);
109
+ return {
110
+ draft: cloneDraft(next),
111
+ update: {
112
+ revision: next.revision,
113
+ ...(textChanged ? { text: next.text } : {}),
114
+ ...(attachmentsChanged
115
+ ? { attachments: next.attachments.map(cloneAttachment) }
116
+ : {}),
117
+ },
118
+ };
119
+ }
120
+
121
+ public snapshot(keyArg: string, revisionArg: number): IControllerSessionDraft {
122
+ const current = this.drafts.get(keyArg) ?? emptyDraft();
123
+ if (current.revision !== revisionArg) {
124
+ throw new ControllerDraftConflictError(cloneDraft(current));
125
+ }
126
+ if (current.revision >= Number.MAX_SAFE_INTEGER) {
127
+ throw new ControllerDraftLimitError('The draft revision limit was reached.');
128
+ }
129
+ if (!current.text.trim()) throw new Error('The composer draft text must not be blank.');
130
+ return cloneDraft(current);
131
+ }
132
+
133
+ public clearExact(
134
+ keyArg: string,
135
+ revisionArg: number,
136
+ ): IControllerDraftMutationResult {
137
+ const current = this.drafts.get(keyArg);
138
+ if (!current || current.revision !== revisionArg) {
139
+ return { draft: cloneDraft(current ?? emptyDraft()) };
140
+ }
141
+ if (current.revision >= Number.MAX_SAFE_INTEGER) {
142
+ throw new ControllerDraftLimitError('The draft revision limit was reached.');
143
+ }
144
+ const next: IControllerSessionDraft = {
145
+ text: '',
146
+ attachments: [],
147
+ revision: current.revision + 1,
148
+ };
149
+ this.store(keyArg, current, next);
150
+ return {
151
+ draft: cloneDraft(next),
152
+ update: { revision: next.revision, text: '', attachments: [] },
153
+ };
154
+ }
155
+
156
+ public purge(keyArg: string): void {
157
+ const current = this.drafts.get(keyArg);
158
+ if (!current) return;
159
+ this.drafts.delete(keyArg);
160
+ if (isNonEmpty(current)) this.nonEmptyDrafts -= 1;
161
+ this.aggregateAttachmentBytes -= attachmentBytes(current);
162
+ }
163
+
164
+ public purgePrefix(prefixArg: string): void {
165
+ for (const key of this.drafts.keys()) {
166
+ if (key.startsWith(prefixArg)) this.purge(key);
167
+ }
168
+ }
169
+
170
+ public clear(): void {
171
+ this.drafts.clear();
172
+ this.nonEmptyDrafts = 0;
173
+ this.aggregateAttachmentBytes = 0;
174
+ }
175
+
176
+ private assertDraftFields(
177
+ textArg: string,
178
+ attachmentsArg: readonly IControllerDraftAttachment[],
179
+ ): void {
180
+ if (Buffer.byteLength(textArg, 'utf8') > controllerMaxDraftTextBytes) {
181
+ throw new ControllerDraftLimitError('The draft text exceeds the controller byte limit.');
182
+ }
183
+ if (attachmentsArg.length > controllerMaxDraftAttachments) {
184
+ throw new ControllerDraftLimitError('The draft attachment count exceeds the controller limit.');
185
+ }
186
+ const ids = new Set<string>();
187
+ let bytes = 0;
188
+ for (const attachment of attachmentsArg) {
189
+ if (ids.has(attachment.id)) throw new Error('Draft attachment IDs must be unique.');
190
+ ids.add(attachment.id);
191
+ if (attachment.size > controllerMaxDraftAttachmentBytes) {
192
+ throw new ControllerDraftLimitError('A draft attachment exceeds the controller byte limit.');
193
+ }
194
+ bytes += attachment.size;
195
+ }
196
+ if (bytes > controllerMaxDraftAttachmentTotalBytes) {
197
+ throw new ControllerDraftLimitError('The draft attachments exceed the controller total byte limit.');
198
+ }
199
+ }
200
+
201
+ private assertAdmission(
202
+ keyArg: string,
203
+ currentArg: IControllerSessionDraft,
204
+ nextArg: IControllerSessionDraft,
205
+ ): void {
206
+ if (!this.drafts.has(keyArg) && this.drafts.size >= maximumDraftIdentities) {
207
+ throw new ControllerDraftLimitError('The controller draft identity limit is exhausted.');
208
+ }
209
+ const nextNonEmptyCount = this.nonEmptyDrafts
210
+ - (isNonEmpty(currentArg) ? 1 : 0)
211
+ + (isNonEmpty(nextArg) ? 1 : 0);
212
+ if (nextNonEmptyCount > maximumNonEmptyDrafts) {
213
+ throw new ControllerDraftLimitError('The controller non-empty draft limit is exhausted.');
214
+ }
215
+ const nextAttachmentBytes = this.aggregateAttachmentBytes
216
+ - attachmentBytes(currentArg)
217
+ + attachmentBytes(nextArg);
218
+ if (nextAttachmentBytes > maximumAggregateAttachmentBytes) {
219
+ throw new ControllerDraftLimitError('The controller draft attachment memory limit is exhausted.');
220
+ }
221
+ }
222
+
223
+ private store(
224
+ keyArg: string,
225
+ currentArg: IControllerSessionDraft,
226
+ nextArg: IControllerSessionDraft,
227
+ ): void {
228
+ this.nonEmptyDrafts += (isNonEmpty(nextArg) ? 1 : 0) - (isNonEmpty(currentArg) ? 1 : 0);
229
+ this.aggregateAttachmentBytes += attachmentBytes(nextArg) - attachmentBytes(currentArg);
230
+ this.drafts.set(keyArg, nextArg);
231
+ }
232
+ }