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,517 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as interfaces from '../ts_interfaces/index.js';
3
+
4
+ const controllerEventTypes: ReadonlySet<string> = new Set(interfaces.controllerEventTypes);
5
+ const controllerRequestTimeoutMs = 35_000;
6
+
7
+ const isQualifiedRuntimeId = (
8
+ valueArg: unknown,
9
+ allowedHarnessIdsArg: ReadonlySet<interfaces.TControllerHarnessId>,
10
+ ): valueArg is interfaces.IControllerRuntimeId => {
11
+ if (!valueArg || typeof valueArg !== 'object') return false;
12
+ const candidate = valueArg as Partial<interfaces.IControllerRuntimeId>;
13
+ return (
14
+ Object.keys(candidate).length === 2
15
+ && typeof candidate.harnessId === 'string'
16
+ && allowedHarnessIdsArg.has(candidate.harnessId as interfaces.TControllerHarnessId)
17
+ && typeof candidate.nativeId === 'string'
18
+ && candidate.nativeId.length > 0
19
+ && new TextEncoder().encode(candidate.nativeId).byteLength <= 512
20
+ );
21
+ };
22
+
23
+ const sessionHarnessIds: ReadonlySet<interfaces.TControllerHarnessId> = new Set(['opencode', 'flex']);
24
+ const terminalHarnessIds: ReadonlySet<interfaces.TControllerHarnessId> = new Set(['controller']);
25
+ const toolExecutionKeys = new Set([
26
+ 'sessionId', 'messageId', 'partId', 'callId', 'toolName', 'status', 'input', 'output',
27
+ 'exitCode', 'errorText', 'childSessionId', 'model', 'startedAt', 'finishedAt',
28
+ 'order', 'sourceUpdatedAt', 'revision', 'streamEpoch',
29
+ ]);
30
+
31
+ const reasoningUpdateKeys = new Set([
32
+ 'sessionId', 'messageId', 'partId', 'text', 'status', 'order',
33
+ 'sourceUpdatedAt', 'revision', 'streamEpoch',
34
+ ]);
35
+ const textUpdateKeys = new Set([
36
+ 'sessionId', 'messageId', 'partId', 'text', 'status', 'order',
37
+ 'sourceUpdatedAt', 'revision', 'streamEpoch',
38
+ ]);
39
+
40
+ const isOptionalFiniteNumber = (valueArg: unknown): boolean => (
41
+ valueArg === undefined || (typeof valueArg === 'number' && Number.isFinite(valueArg))
42
+ );
43
+
44
+ const isTranscriptOrder = (valueArg: unknown): boolean => {
45
+ if (!valueArg || typeof valueArg !== 'object') return false;
46
+ const candidate = valueArg as Partial<interfaces.IControllerTranscriptOrder>;
47
+ return Object.keys(candidate).length === 2
48
+ && Number.isSafeInteger(candidate.messageIndex)
49
+ && (candidate.messageIndex ?? -1) >= 0
50
+ && Number.isSafeInteger(candidate.partIndex)
51
+ && (candidate.partIndex ?? -1) >= 0;
52
+ };
53
+
54
+ const serializedBytesWithin = (valueArg: unknown, byteLimitArg: number): boolean => {
55
+ try {
56
+ return new TextEncoder().encode(JSON.stringify(valueArg)).byteLength <= byteLimitArg;
57
+ } catch {
58
+ return false;
59
+ }
60
+ };
61
+
62
+ const isDraftAttachment = (valueArg: unknown): valueArg is interfaces.IControllerDraftAttachment => {
63
+ if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false;
64
+ const candidate = valueArg as Partial<interfaces.IControllerDraftAttachment>;
65
+ if (
66
+ !Object.keys(candidate).every((key) => (
67
+ ['id', 'name', 'mediaType', 'size', 'kind', 'dataBase64'].includes(key)
68
+ ))
69
+ || typeof candidate.id !== 'string'
70
+ || candidate.id.length === 0
71
+ || new TextEncoder().encode(candidate.id).byteLength > 512
72
+ || typeof candidate.name !== 'string'
73
+ || candidate.name.length === 0
74
+ || new TextEncoder().encode(candidate.name).byteLength > 255
75
+ || typeof candidate.mediaType !== 'string'
76
+ || candidate.mediaType.length === 0
77
+ || new TextEncoder().encode(candidate.mediaType).byteLength > 129
78
+ || !Number.isSafeInteger(candidate.size)
79
+ || (candidate.size ?? -1) < 0
80
+ || (candidate.size ?? 0) > interfaces.controllerMaxDraftAttachmentBytes
81
+ || !['image', 'text', 'binary'].includes(candidate.kind ?? '')
82
+ || typeof candidate.dataBase64 !== 'string'
83
+ ) return false;
84
+ try {
85
+ const decoded = globalThis.atob(candidate.dataBase64);
86
+ return decoded.length === candidate.size && globalThis.btoa(decoded) === candidate.dataBase64;
87
+ } catch {
88
+ return false;
89
+ }
90
+ };
91
+
92
+ const isSessionDraftUpdate = (
93
+ valueArg: unknown,
94
+ ): valueArg is interfaces.IControllerSessionDraftUpdate => {
95
+ if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false;
96
+ const candidate = valueArg as Partial<interfaces.IControllerSessionDraftUpdate>;
97
+ if (
98
+ !Object.keys(candidate).every((key) => ['revision', 'text', 'attachments'].includes(key))
99
+ || !Number.isSafeInteger(candidate.revision)
100
+ || (candidate.revision ?? -1) < 0
101
+ || (candidate.text === undefined && candidate.attachments === undefined)
102
+ || (candidate.text !== undefined && (
103
+ typeof candidate.text !== 'string'
104
+ || new TextEncoder().encode(candidate.text).byteLength > interfaces.controllerMaxDraftTextBytes
105
+ ))
106
+ || (candidate.attachments !== undefined && (
107
+ !Array.isArray(candidate.attachments)
108
+ || candidate.attachments.length > interfaces.controllerMaxDraftAttachments
109
+ || !candidate.attachments.every(isDraftAttachment)
110
+ || candidate.attachments.reduce((sum, attachment) => sum + attachment.size, 0)
111
+ > interfaces.controllerMaxDraftAttachmentTotalBytes
112
+ ))
113
+ ) return false;
114
+ return true;
115
+ };
116
+
117
+ const isControllerToolExecution = (
118
+ valueArg: unknown,
119
+ ): valueArg is interfaces.IControllerToolExecution => {
120
+ if (!valueArg || typeof valueArg !== 'object') return false;
121
+ const candidate = valueArg as Partial<interfaces.IControllerToolExecution>;
122
+ if (!isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) return false;
123
+ const executionHarnessIds: ReadonlySet<interfaces.TControllerHarnessId> = new Set([
124
+ candidate.sessionId.harnessId,
125
+ ]);
126
+ return (
127
+ Object.keys(candidate).every((key) => toolExecutionKeys.has(key))
128
+ && isQualifiedRuntimeId(candidate.messageId, executionHarnessIds)
129
+ && isQualifiedRuntimeId(candidate.partId, executionHarnessIds)
130
+ && isQualifiedRuntimeId(candidate.callId, executionHarnessIds)
131
+ && typeof candidate.toolName === 'string'
132
+ && candidate.toolName.length > 0
133
+ && new TextEncoder().encode(candidate.toolName).byteLength <= 2048
134
+ && ['pending', 'running', 'completed', 'error'].includes(candidate.status ?? '')
135
+ && (candidate.exitCode === undefined
136
+ || (Number.isSafeInteger(candidate.exitCode) && candidate.exitCode >= 0))
137
+ && (candidate.errorText === undefined || typeof candidate.errorText === 'string')
138
+ && (candidate.childSessionId === undefined
139
+ || isQualifiedRuntimeId(candidate.childSessionId, executionHarnessIds))
140
+ && (candidate.model === undefined || typeof candidate.model === 'string')
141
+ && isOptionalFiniteNumber(candidate.startedAt)
142
+ && isOptionalFiniteNumber(candidate.finishedAt)
143
+ && (candidate.order === undefined || isTranscriptOrder(candidate.order))
144
+ && (candidate.sessionId.harnessId !== 'flex' || candidate.order !== undefined)
145
+ && (candidate.sessionId.harnessId !== 'opencode' || candidate.order === undefined)
146
+ && typeof candidate.sourceUpdatedAt === 'number'
147
+ && Number.isFinite(candidate.sourceUpdatedAt)
148
+ && candidate.sourceUpdatedAt >= 0
149
+ && Number.isSafeInteger(candidate.revision)
150
+ && (candidate.revision ?? -1) >= 0
151
+ && Number.isSafeInteger(candidate.streamEpoch)
152
+ && (candidate.streamEpoch ?? -1) >= 0
153
+ );
154
+ };
155
+
156
+ const isControllerReasoningUpdate = (
157
+ valueArg: unknown,
158
+ ): valueArg is interfaces.IControllerReasoningUpdate => {
159
+ if (!valueArg || typeof valueArg !== 'object') return false;
160
+ const candidate = valueArg as Partial<interfaces.IControllerReasoningUpdate>;
161
+ if (!isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) return false;
162
+ const harnessIds: ReadonlySet<interfaces.TControllerHarnessId> = new Set([
163
+ candidate.sessionId.harnessId,
164
+ ]);
165
+ return Object.keys(candidate).every((key) => reasoningUpdateKeys.has(key))
166
+ && isQualifiedRuntimeId(candidate.messageId, harnessIds)
167
+ && isQualifiedRuntimeId(candidate.partId, harnessIds)
168
+ && typeof candidate.text === 'string'
169
+ && new TextEncoder().encode(candidate.text).byteLength
170
+ <= interfaces.controllerMaxLiveMessageEventBytes
171
+ && ['running', 'completed', 'cancelled'].includes(candidate.status ?? '')
172
+ && (candidate.order === undefined || isTranscriptOrder(candidate.order))
173
+ && (candidate.sessionId.harnessId !== 'flex' || candidate.order !== undefined)
174
+ && (candidate.sessionId.harnessId !== 'opencode' || candidate.order === undefined)
175
+ && typeof candidate.sourceUpdatedAt === 'number'
176
+ && Number.isFinite(candidate.sourceUpdatedAt)
177
+ && Number.isSafeInteger(candidate.revision)
178
+ && (candidate.revision ?? -1) >= 0
179
+ && Number.isSafeInteger(candidate.streamEpoch)
180
+ && (candidate.streamEpoch ?? -1) >= 0;
181
+ };
182
+
183
+ const isControllerTextUpdate = (
184
+ valueArg: unknown,
185
+ ): valueArg is interfaces.IControllerTextUpdate => {
186
+ if (!valueArg || typeof valueArg !== 'object') return false;
187
+ const candidate = valueArg as Partial<interfaces.IControllerTextUpdate>;
188
+ if (!isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) return false;
189
+ const harnessIds: ReadonlySet<interfaces.TControllerHarnessId> = new Set([
190
+ candidate.sessionId.harnessId,
191
+ ]);
192
+ return Object.keys(candidate).every((key) => textUpdateKeys.has(key))
193
+ && isQualifiedRuntimeId(candidate.messageId, harnessIds)
194
+ && isQualifiedRuntimeId(candidate.partId, harnessIds)
195
+ && typeof candidate.text === 'string'
196
+ && new TextEncoder().encode(candidate.text).byteLength
197
+ <= interfaces.controllerMaxLiveMessageEventBytes
198
+ && ['running', 'completed'].includes(candidate.status ?? '')
199
+ && (candidate.order === undefined || isTranscriptOrder(candidate.order))
200
+ && (candidate.sessionId.harnessId !== 'flex' || candidate.order !== undefined)
201
+ && (candidate.sessionId.harnessId !== 'opencode' || candidate.order === undefined)
202
+ && typeof candidate.sourceUpdatedAt === 'number'
203
+ && Number.isFinite(candidate.sourceUpdatedAt)
204
+ && Number.isSafeInteger(candidate.revision)
205
+ && (candidate.revision ?? -1) >= 0
206
+ && Number.isSafeInteger(candidate.streamEpoch)
207
+ && (candidate.streamEpoch ?? -1) >= 0;
208
+ };
209
+
210
+ export interface IControllerSocketClientOptions {
211
+ onStatus: (status: plugins.typedsocket.TConnectionStatus) => void;
212
+ onControllerEvent: (event: interfaces.IControllerEvent) => void | Promise<void>;
213
+ /** Streamed PTY output for a terminal this client may be viewing. */
214
+ onTerminalOutput?: (
215
+ output: interfaces.IReq_ControllerTerminalOutput['request'],
216
+ ) => void | Promise<void>;
217
+ onBrowserViewEvent?: (
218
+ event: TControllerBrowserViewEventMessage,
219
+ ) => void | Promise<void>;
220
+ /** Fires when the transport fail-closes the connection (e.g. frame limits). */
221
+ onTransportClose?: (closeCode: number, reason: string) => void;
222
+ }
223
+
224
+ export type TControllerBrowserViewEventMessage =
225
+ plugins.typedrequest.TReverseVirtualStreamDirections<
226
+ interfaces.IReq_ControllerBrowserViewEvent['request']
227
+ >;
228
+
229
+ export interface IControllerSocketRequestOptions
230
+ extends plugins.typedrequest.ITypedRequestFireOptions {
231
+ abortSignal?: AbortSignal;
232
+ }
233
+
234
+ export const isControllerEvent = (valueArg: unknown): valueArg is interfaces.IControllerEvent => {
235
+ if (!valueArg || typeof valueArg !== 'object') {
236
+ return false;
237
+ }
238
+
239
+ const candidate = valueArg as Partial<interfaces.IControllerEvent>;
240
+ const hasSessionMarker = candidate.sessionStatus !== undefined || candidate.sessionError !== undefined;
241
+ const hasToolExecution = candidate.toolExecution !== undefined;
242
+ const hasReasoningUpdate = candidate.reasoningUpdate !== undefined;
243
+ const hasTextUpdate = candidate.textUpdate !== undefined;
244
+ const hasDraftUpdate = candidate.sessionDraftUpdate !== undefined;
245
+ const hasUpgrade = candidate.upgrade !== undefined;
246
+ return (
247
+ typeof candidate.type === 'string'
248
+ && controllerEventTypes.has(candidate.type as interfaces.TControllerEventType)
249
+ && typeof candidate.timestamp === 'number'
250
+ && Number.isFinite(candidate.timestamp)
251
+ && (candidate.harnessId === undefined
252
+ || candidate.harnessId === 'opencode'
253
+ || candidate.harnessId === 'flex')
254
+ && (candidate.sessionId === undefined
255
+ || isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds))
256
+ && (candidate.projectId === undefined
257
+ || (typeof candidate.projectId === 'string' && candidate.projectId.length > 0))
258
+ && (
259
+ candidate.sessionStatus === undefined
260
+ || ['idle', 'busy', 'retry', 'error'].includes(candidate.sessionStatus)
261
+ )
262
+ && (candidate.sessionError === undefined || candidate.sessionError === true)
263
+ && (candidate.toolStreamEpoch === undefined || (
264
+ Number.isSafeInteger(candidate.toolStreamEpoch) && candidate.toolStreamEpoch >= 0
265
+ ))
266
+ && (candidate.messageStreamEpoch === undefined || (
267
+ Number.isSafeInteger(candidate.messageStreamEpoch) && candidate.messageStreamEpoch >= 0
268
+ ))
269
+ && (candidate.toolExecution === undefined || isControllerToolExecution(candidate.toolExecution))
270
+ && (candidate.reasoningUpdate === undefined
271
+ || isControllerReasoningUpdate(candidate.reasoningUpdate))
272
+ && (candidate.textUpdate === undefined || isControllerTextUpdate(candidate.textUpdate))
273
+ && (candidate.sessionDraftUpdate === undefined
274
+ || isSessionDraftUpdate(candidate.sessionDraftUpdate))
275
+ && (!hasToolExecution || serializedBytesWithin(
276
+ candidate,
277
+ interfaces.controllerMaxToolEventBytes,
278
+ ))
279
+ && (!(hasReasoningUpdate || hasTextUpdate) || serializedBytesWithin(
280
+ candidate,
281
+ interfaces.controllerMaxLiveMessageEventBytes,
282
+ ))
283
+ && (!hasSessionMarker || (
284
+ candidate.type === 'session.changed'
285
+ && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)
286
+ ))
287
+ && (
288
+ candidate.harnessId === undefined
289
+ || candidate.sessionId === undefined
290
+ || candidate.harnessId === candidate.sessionId.harnessId
291
+ )
292
+ && !(candidate.sessionStatus !== undefined && candidate.sessionError === true)
293
+ && (!hasToolExecution || (
294
+ candidate.type === 'session.tool.updated'
295
+ && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex')
296
+ && typeof candidate.projectId === 'string'
297
+ && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)
298
+ && candidate.harnessId === candidate.sessionId.harnessId
299
+ && candidate.harnessId === candidate.toolExecution!.sessionId.harnessId
300
+ && interfaces.controllerRuntimeIdKey(candidate.sessionId)
301
+ === interfaces.controllerRuntimeIdKey(candidate.toolExecution!.sessionId)
302
+ && candidate.toolStreamEpoch === undefined
303
+ ))
304
+ && (hasToolExecution || candidate.type !== 'session.tool.updated')
305
+ && (!hasReasoningUpdate || (
306
+ candidate.type === 'session.reasoning.updated'
307
+ && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex')
308
+ && typeof candidate.projectId === 'string'
309
+ && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)
310
+ && candidate.harnessId === candidate.reasoningUpdate!.sessionId.harnessId
311
+ && interfaces.controllerRuntimeIdKey(candidate.sessionId)
312
+ === interfaces.controllerRuntimeIdKey(candidate.reasoningUpdate!.sessionId)
313
+ ))
314
+ && (hasReasoningUpdate || candidate.type !== 'session.reasoning.updated')
315
+ && (!hasTextUpdate || (
316
+ candidate.type === 'session.text.updated'
317
+ && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex')
318
+ && typeof candidate.projectId === 'string'
319
+ && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)
320
+ && candidate.harnessId === candidate.textUpdate!.sessionId.harnessId
321
+ && interfaces.controllerRuntimeIdKey(candidate.sessionId)
322
+ === interfaces.controllerRuntimeIdKey(candidate.textUpdate!.sessionId)
323
+ ))
324
+ && (hasTextUpdate || candidate.type !== 'session.text.updated')
325
+ && (!hasDraftUpdate || (
326
+ candidate.type === 'session.draft.changed'
327
+ && typeof candidate.projectId === 'string'
328
+ && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)
329
+ ))
330
+ && (hasDraftUpdate || candidate.type !== 'session.draft.changed')
331
+ && (candidate.toolStreamEpoch === undefined || (
332
+ candidate.type === 'harness.changed'
333
+ && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex')
334
+ && !hasToolExecution
335
+ ))
336
+ && (candidate.messageStreamEpoch === undefined || (
337
+ candidate.type === 'harness.changed'
338
+ && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex')
339
+ && !hasReasoningUpdate
340
+ && !hasTextUpdate
341
+ ))
342
+ && (candidate.upgrade === undefined || (
343
+ typeof candidate.upgrade === 'object'
344
+ && candidate.upgrade !== null
345
+ && Object.keys(candidate.upgrade).length === 3
346
+ && typeof candidate.upgrade.fromVersion === 'string'
347
+ && candidate.upgrade.fromVersion.length > 0
348
+ && new TextEncoder().encode(candidate.upgrade.fromVersion).byteLength <= 128
349
+ && typeof candidate.upgrade.toVersion === 'string'
350
+ && candidate.upgrade.toVersion.length > 0
351
+ && new TextEncoder().encode(candidate.upgrade.toVersion).byteLength <= 128
352
+ && ['preparing', 'pausing', 'installing', 'restarting', 'continuing', 'completed', 'failed']
353
+ .includes(candidate.upgrade.phase)
354
+ ))
355
+ && (!hasUpgrade || candidate.type === 'upgrade.changed')
356
+ && (hasUpgrade || candidate.type !== 'upgrade.changed')
357
+ );
358
+ };
359
+
360
+ export const isControllerTerminalOutput = (
361
+ valueArg: unknown,
362
+ ): valueArg is interfaces.IReq_ControllerTerminalOutput['request'] => {
363
+ if (!valueArg || typeof valueArg !== 'object') return false;
364
+ const candidate = valueArg as Partial<interfaces.IReq_ControllerTerminalOutput['request']>;
365
+ return (
366
+ isQualifiedRuntimeId(candidate.terminalId, terminalHarnessIds)
367
+ && typeof candidate.dataBase64 === 'string'
368
+ && (candidate.reset === undefined || typeof candidate.reset === 'boolean')
369
+ && (candidate.ended === undefined || typeof candidate.ended === 'boolean')
370
+ );
371
+ };
372
+
373
+ export class ControllerSocketClient {
374
+ private readonly typedRouter = new plugins.typedrequest.TypedRouter();
375
+ private typedSocket: plugins.typedsocket.TypedSocket | undefined;
376
+ private statusSubscription: { unsubscribe: () => void } | undefined;
377
+ private diagnosticsSubscription: { unsubscribe: () => void } | undefined;
378
+ private abortController: AbortController | undefined;
379
+ private startPromise: Promise<void> | undefined;
380
+
381
+ constructor(private readonly options: IControllerSocketClientOptions) {
382
+ this.typedRouter.addTypedHandler(
383
+ new plugins.typedrequest.TypedHandler<interfaces.IReq_ControllerEvent>(
384
+ 'controller.event',
385
+ async (eventArg) => {
386
+ if (isControllerEvent(eventArg)) {
387
+ await this.options.onControllerEvent(eventArg);
388
+ }
389
+ return { received: true };
390
+ },
391
+ ),
392
+ );
393
+ this.typedRouter.addTypedHandler(
394
+ new plugins.typedrequest.TypedHandler<interfaces.IReq_ControllerTerminalOutput>(
395
+ 'controller.terminal.output',
396
+ async (outputArg) => {
397
+ if (isControllerTerminalOutput(outputArg)) {
398
+ await this.options.onTerminalOutput?.(outputArg);
399
+ }
400
+ return {};
401
+ },
402
+ ),
403
+ );
404
+ this.typedRouter.addTypedHandler(
405
+ new plugins.typedrequest.TypedHandler<interfaces.IReq_ControllerBrowserViewEvent>(
406
+ 'controller.browser.view.event',
407
+ async (eventArg) => {
408
+ await this.options.onBrowserViewEvent?.(eventArg);
409
+ return {};
410
+ },
411
+ ),
412
+ );
413
+ }
414
+
415
+ public get status(): plugins.typedsocket.TConnectionStatus {
416
+ return this.typedSocket?.getStatus() ?? 'new';
417
+ }
418
+
419
+ public get isConnected(): boolean {
420
+ return this.status === 'connected';
421
+ }
422
+
423
+ public async start(): Promise<void> {
424
+ if (this.typedSocket?.getStatus() === 'connected') {
425
+ return;
426
+ }
427
+ if (this.startPromise) {
428
+ return this.startPromise;
429
+ }
430
+
431
+ const startPromise = this.startInternal();
432
+ this.startPromise = startPromise;
433
+ try {
434
+ await startPromise;
435
+ } finally {
436
+ if (this.startPromise === startPromise) {
437
+ this.startPromise = undefined;
438
+ }
439
+ }
440
+ }
441
+
442
+ public async stop(): Promise<void> {
443
+ this.abortController?.abort();
444
+ this.abortController = undefined;
445
+ this.statusSubscription?.unsubscribe();
446
+ this.statusSubscription = undefined;
447
+ this.diagnosticsSubscription?.unsubscribe();
448
+ this.diagnosticsSubscription = undefined;
449
+
450
+ const typedSocket = this.typedSocket;
451
+ this.typedSocket = undefined;
452
+ if (typedSocket) {
453
+ await typedSocket.stop();
454
+ }
455
+
456
+ if (this.startPromise) {
457
+ await this.startPromise.catch(() => undefined);
458
+ }
459
+ }
460
+
461
+ public async fire<TRequest extends interfaces.TControllerRequest>(
462
+ method: TRequest['method'],
463
+ request: TRequest['request'],
464
+ optionsArg?: IControllerSocketRequestOptions,
465
+ ): Promise<TRequest['response']> {
466
+ const typedSocket = this.typedSocket;
467
+ if (!typedSocket || typedSocket.getStatus() !== 'connected') {
468
+ throw new Error('The controller connection is not available.');
469
+ }
470
+ const timeoutMs = optionsArg?.timeoutMs ?? controllerRequestTimeoutMs;
471
+ const { abortSignal, ...fireOptions } = optionsArg ?? {};
472
+ return typedSocket.createTypedRequest<TRequest>(method, undefined, {
473
+ timeoutMs,
474
+ abortSignal,
475
+ }).fire(request, {
476
+ ...fireOptions,
477
+ timeoutMs,
478
+ });
479
+ }
480
+
481
+ private async startInternal(): Promise<void> {
482
+ this.options.onStatus('connecting');
483
+ this.abortController = new AbortController();
484
+
485
+ const typedSocket = await plugins.typedsocket.TypedSocket.createClient(
486
+ this.typedRouter,
487
+ plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl(),
488
+ {
489
+ autoReconnect: true,
490
+ maxRetries: 100,
491
+ initialBackoffMs: 500,
492
+ maxBackoffMs: 10_000,
493
+ abortSignal: this.abortController.signal,
494
+ },
495
+ );
496
+ this.typedSocket = typedSocket;
497
+ this.statusSubscription = typedSocket.statusSubject.subscribe((statusArg) => {
498
+ this.options.onStatus(statusArg);
499
+ });
500
+ this.diagnosticsSubscription = typedSocket.diagnosticsSubject.subscribe((diagnosticArg) => {
501
+ if (
502
+ diagnosticArg.kind === 'connectionClosed'
503
+ && typeof diagnosticArg.closeCode === 'number'
504
+ && diagnosticArg.closeCode !== 1000
505
+ ) {
506
+ this.options.onTransportClose?.(
507
+ diagnosticArg.closeCode,
508
+ typeof diagnosticArg.reason === 'string' ? diagnosticArg.reason : '',
509
+ );
510
+ }
511
+ });
512
+
513
+ // createClient resolves after the first connected emission, so subscribers
514
+ // installed here intentionally receive the current state once explicitly.
515
+ this.options.onStatus(typedSocket.getStatus());
516
+ }
517
+ }