agl 40.5.1 → 41.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/changelog.md +43 -0
  2. package/dist_serve/bundle.js +3514 -3357
  3. package/dist_serve/bundle.js.map +2 -2
  4. package/dist_serve/bundle.js.third-party-notices.json +58 -32
  5. package/dist_serve/bundle.js.third-party-notices.txt +108 -33
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/aglapi/aglapisurface.document.js +64 -37
  8. package/dist_ts/classes.aglhome.d.ts +5 -0
  9. package/dist_ts/classes.aglhome.js +2 -1
  10. package/dist_ts/classes.authstore.js +5 -1
  11. package/dist_ts/classes.controller.d.ts +8 -0
  12. package/dist_ts/classes.controller.js +119 -22
  13. package/dist_ts/classes.controllereventdelivery.d.ts +72 -0
  14. package/dist_ts/classes.controllereventdelivery.js +233 -0
  15. package/dist_ts/classes.flexmodels.public.js +4 -1
  16. package/dist_ts/classes.flexprojectionreader.d.ts +6 -0
  17. package/dist_ts/classes.flexprojectionreader.js +29 -5
  18. package/dist_ts/classes.flexservice.d.ts +2 -1
  19. package/dist_ts/classes.flexservice.js +34 -6
  20. package/dist_ts/classes.flexstore.d.ts +41 -4
  21. package/dist_ts/classes.flexstore.js +266 -203
  22. package/dist_ts/classes.inferencegateway.d.ts +35 -1
  23. package/dist_ts/classes.inferencegateway.js +86 -26
  24. package/dist_ts/classes.inferenceservice.d.ts +4 -2
  25. package/dist_ts/classes.inferenceservice.js +5 -4
  26. package/dist_ts/classes.proxycontrol.d.ts +58 -4
  27. package/dist_ts/classes.proxycontrol.js +23 -44
  28. package/dist_ts/classes.proxycontrolclient.d.ts +15 -5
  29. package/dist_ts/classes.proxycontrolclient.js +67 -17
  30. package/dist_ts/classes.proxydaemon.d.ts +22 -3
  31. package/dist_ts/classes.proxydaemon.js +73 -12
  32. package/dist_ts/classes.proxyleaseholder.d.ts +43 -14
  33. package/dist_ts/classes.proxyleaseholder.js +66 -22
  34. package/dist_ts/classes.proxystore.d.ts +9 -0
  35. package/dist_ts/classes.proxystore.js +15 -7
  36. package/dist_ts/classes.ticketnotifier.js +4 -2
  37. package/dist_ts/classes.ticketstore.d.ts +50 -2
  38. package/dist_ts/classes.ticketstore.js +195 -78
  39. package/dist_ts/classes.workercallerbindings.d.ts +66 -0
  40. package/dist_ts/classes.workercallerbindings.js +211 -0
  41. package/dist_ts/classes.workercallers.d.ts +63 -2
  42. package/dist_ts/classes.workercallers.js +430 -65
  43. package/dist_ts/driver/classes.drivereventrelay.d.ts +13 -1
  44. package/dist_ts/driver/classes.drivereventrelay.js +99 -6
  45. package/dist_ts/driver/functions.driverinstructions.d.ts +1 -1
  46. package/dist_ts/driver/functions.driverinstructions.js +1 -1
  47. package/dist_ts/driver/functions.drivertools.d.ts +11 -2
  48. package/dist_ts/driver/functions.drivertools.js +19 -9
  49. package/dist_ts/fleet/classes.boardpump.d.ts +8 -2
  50. package/dist_ts/fleet/classes.boardpump.js +38 -20
  51. package/dist_ts/fleet/classes.ticketeffects.d.ts +17 -2
  52. package/dist_ts/fleet/classes.ticketeffects.js +16 -10
  53. package/dist_ts/fleet/classes.workerlaunches.d.ts +12 -5
  54. package/dist_ts/fleet/classes.workerlaunches.js +25 -19
  55. package/dist_ts/fleet/classes.workers.d.ts +21 -0
  56. package/dist_ts/fleet/classes.workers.js +110 -17
  57. package/dist_ts/fleet/functions.workervalidation.js +2 -2
  58. package/dist_ts/functions.flexprivatesnapshot.d.ts +102 -0
  59. package/dist_ts/functions.flexprivatesnapshot.js +461 -0
  60. package/dist_ts/functions.inferencecli.d.ts +3 -2
  61. package/dist_ts/functions.inferencecli.js +67 -5
  62. package/dist_ts/functions.inferencecliarguments.d.ts +6 -2
  63. package/dist_ts/functions.inferencecliarguments.js +29 -2
  64. package/dist_ts/functions.inferencelaunch.d.ts +24 -0
  65. package/dist_ts/functions.inferencelaunch.js +42 -2
  66. package/dist_ts/functions.mcptickettools.d.ts +3 -0
  67. package/dist_ts/functions.mcptickettools.js +2 -2
  68. package/dist_ts/functions.proxycontract.d.ts +11 -0
  69. package/dist_ts/functions.proxycontract.js +12 -1
  70. package/dist_ts/functions.proxysocket.d.ts +6 -0
  71. package/dist_ts/functions.proxysocket.js +46 -0
  72. package/dist_ts/functions.ticketcli.d.ts +1 -1
  73. package/dist_ts/functions.ticketcli.js +8 -4
  74. package/dist_ts/functions.ticketvalidation.d.ts +3 -0
  75. package/dist_ts/functions.ticketvalidation.js +7 -4
  76. package/dist_ts/functions.workerprocessproof.d.ts +6 -0
  77. package/dist_ts/functions.workerprocessproof.js +28 -4
  78. package/dist_ts/interfaces.flexipc.d.ts +21 -2
  79. package/dist_ts/interfaces.flexipc.js +33 -16
  80. package/dist_ts_interfaces/fleetworkers.d.ts +6 -0
  81. package/dist_ts_interfaces/interfaces.d.ts +1 -1
  82. package/dist_ts_interfaces/interfaces.js +1 -1
  83. package/dist_ts_interfaces/requests.tickets.d.ts +8 -12
  84. package/dist_ts_interfaces/tickets.d.ts +37 -9
  85. package/dist_ts_interfaces/tickets.js +7 -1
  86. package/dist_ts_interfaces/workercallers.d.ts +43 -4
  87. package/package.json +7 -7
  88. package/readme.md +156 -23
  89. package/ts/00_commitinfo_data.ts +1 -1
  90. package/ts/aglapi/aglapisurface.document.ts +63 -36
  91. package/ts/classes.aglhome.ts +6 -0
  92. package/ts/classes.authstore.ts +4 -0
  93. package/ts/classes.controller.ts +141 -32
  94. package/ts/classes.controllereventdelivery.ts +289 -0
  95. package/ts/classes.flexmodels.public.ts +10 -0
  96. package/ts/classes.flexprojectionreader.ts +30 -3
  97. package/ts/classes.flexservice.ts +33 -5
  98. package/ts/classes.flexstore.ts +381 -246
  99. package/ts/classes.inferencegateway.ts +104 -23
  100. package/ts/classes.inferenceservice.ts +6 -3
  101. package/ts/classes.proxycontrol.ts +76 -39
  102. package/ts/classes.proxycontrolclient.ts +72 -20
  103. package/ts/classes.proxydaemon.ts +85 -15
  104. package/ts/classes.proxyleaseholder.ts +84 -28
  105. package/ts/classes.proxystore.ts +16 -5
  106. package/ts/classes.ticketnotifier.ts +3 -1
  107. package/ts/classes.ticketstore.ts +201 -75
  108. package/ts/classes.workercallerbindings.ts +147 -0
  109. package/ts/classes.workercallers.ts +410 -59
  110. package/ts/driver/classes.drivereventrelay.ts +89 -4
  111. package/ts/driver/functions.driverinstructions.ts +1 -1
  112. package/ts/driver/functions.drivertools.ts +22 -8
  113. package/ts/fleet/classes.boardpump.ts +51 -19
  114. package/ts/fleet/classes.ticketeffects.ts +25 -11
  115. package/ts/fleet/classes.workerlaunches.ts +23 -15
  116. package/ts/fleet/classes.workers.ts +79 -17
  117. package/ts/fleet/functions.workervalidation.ts +1 -1
  118. package/ts/functions.flexprivatesnapshot.ts +601 -0
  119. package/ts/functions.inferencecli.ts +61 -7
  120. package/ts/functions.inferencecliarguments.ts +32 -1
  121. package/ts/functions.inferencelaunch.ts +49 -2
  122. package/ts/functions.mcptickettools.ts +1 -1
  123. package/ts/functions.proxycontract.ts +12 -0
  124. package/ts/functions.proxysocket.ts +39 -0
  125. package/ts/functions.ticketcli.ts +7 -3
  126. package/ts/functions.ticketvalidation.ts +7 -3
  127. package/ts/functions.workerprocessproof.ts +27 -4
  128. package/ts/interfaces.flexipc.ts +50 -21
  129. package/ts_interfaces/fleetworkers.ts +6 -0
  130. package/ts_interfaces/interfaces.ts +1 -1
  131. package/ts_interfaces/requests.tickets.ts +9 -6
  132. package/ts_interfaces/tickets.ts +32 -1
  133. package/ts_interfaces/workercallers.ts +32 -4
  134. package/ts_web/00_commitinfo_data.ts +1 -1
  135. package/ts_web/elements.harnesscontrollerapp.ts +19 -8
  136. package/ts_web/elements.ticketboard.ts +16 -4
  137. package/ts_web/settings.general.ts +10 -4
@@ -1408,8 +1408,8 @@ export const AGL_API_SURFACE: plugins.flexTypedRequest.IFlexTypedRequestSurface
1408
1408
  {
1409
1409
  "method": "agl.mcp.ticket.get",
1410
1410
  "path": "agl.agl_mcp_ticket_get",
1411
- "summary": "Reads a ticket by its `AGL-<n>` key, with its Markdown comments, source, links, questions and their answers, and its co…",
1412
- "description": "Reads a ticket by its `AGL-<n>` key, with its Markdown comments, source, links, questions and\ntheir answers, and its complete activity log.",
1411
+ "summary": "Reads a ticket by its `AGL-<n>` key with its source, links, all its questions and their answers, and the newest page of…",
1412
+ "description": "Reads a ticket by its `AGL-<n>` key with its source, links, all its questions and their answers,\nand the newest page of its history (up to `limit` entries, default 50, at most 500, within 256 KiB), oldest first: the\nactivity entries and the Markdown comments they name, a `commented` entry naming its comment by\nid. `historyBefore` is present while older history exists; pass it as `before` for the next page.\n`commentId` reads that one comment instead (`comments: [it]`, `activity: []`), without `before` or `limit`.",
1413
1413
  "omitted": [
1414
1414
  "ownerToken"
1415
1415
  ],
@@ -1419,6 +1419,18 @@ export const AGL_API_SURFACE: plugins.flexTypedRequest.IFlexTypedRequestSurface
1419
1419
  "properties": {
1420
1420
  "ticketId": {
1421
1421
  "type": "string"
1422
+ },
1423
+ "before": {
1424
+ "type": "string",
1425
+ "description": "An activity id, as `historyBefore` returned it: the page holds only older entries."
1426
+ },
1427
+ "limit": {
1428
+ "type": "number",
1429
+ "description": "Entries on the page, 1 to 500 (`ticketHistoryPage.maximum`); 50 (`ticketHistoryPage.default`) when absent."
1430
+ },
1431
+ "commentId": {
1432
+ "type": "string",
1433
+ "description": "One comment of this ticket, by the id an activity entry or a fleet event names: the response holds the ticket, its questions and that comment alone (`comments: [it]`, `activity: []`, no `historyBefore`). Excludes `before` and `limit`."
1422
1434
  }
1423
1435
  },
1424
1436
  "required": [
@@ -1426,39 +1438,9 @@ export const AGL_API_SURFACE: plugins.flexTypedRequest.IFlexTypedRequestSurface
1426
1438
  ]
1427
1439
  },
1428
1440
  "outputSchema": {
1429
- "type": "object",
1430
- "properties": {
1431
- "ticket": {
1432
- "$ref": "#/$defs/ITicket"
1433
- },
1434
- "comments": {
1435
- "type": "array",
1436
- "items": {
1437
- "$ref": "#/$defs/ITicketComment"
1438
- }
1439
- },
1440
- "activity": {
1441
- "type": "array",
1442
- "items": {
1443
- "$ref": "#/$defs/ITicketActivity"
1444
- }
1445
- },
1446
- "questions": {
1447
- "type": "array",
1448
- "items": {
1449
- "$ref": "#/$defs/ITicketQuestion"
1450
- }
1451
- }
1452
- },
1453
- "required": [
1454
- "ticket",
1455
- "comments",
1456
- "activity",
1457
- "questions"
1458
- ],
1459
- "additionalProperties": false
1441
+ "$ref": "#/$defs/ITicketDetail"
1460
1442
  },
1461
- "declaration": "/**\n * Reads a ticket by its `AGL-<n>` key, with its Markdown comments, source, links, questions and\n * their answers, and its complete activity log.\n */\nfunction agl_mcp_ticket_get(params: { ticketId: string }): Promise<{ ticket: ITicket; comments: ITicketComment[]; activity: ITicketActivity[]; questions: ITicketQuestion[] }>;"
1443
+ "declaration": "/**\n * Reads a ticket by its `AGL-<n>` key with its source, links, all its questions and their answers,\n * and the newest page of its history (up to `limit` entries, default 50, at most 500, within 256 KiB), oldest first: the\n * activity entries and the Markdown comments they name, a `commented` entry naming its comment by\n * id. `historyBefore` is present while older history exists; pass it as `before` for the next page.\n * `commentId` reads that one comment instead (`comments: [it]`, `activity: []`), without `before` or `limit`.\n */\nfunction agl_mcp_ticket_get(params: ITicketGet): Promise<ITicketDetail>;"
1462
1444
  },
1463
1445
  {
1464
1446
  "method": "agl.mcp.ticket.link",
@@ -3783,7 +3765,7 @@ export const AGL_API_SURFACE: plugins.flexTypedRequest.IFlexTypedRequestSurface
3783
3765
  },
3784
3766
  "protocolVersion": {
3785
3767
  "type": "number",
3786
- "const": 45
3768
+ "const": 46
3787
3769
  },
3788
3770
  "upgradeManagementVersion": {
3789
3771
  "type": "number"
@@ -3905,7 +3887,7 @@ export const AGL_API_SURFACE: plugins.flexTypedRequest.IFlexTypedRequestSurface
3905
3887
  ],
3906
3888
  "additionalProperties": false
3907
3889
  },
3908
- "declaration": "interface IControllerStatus {\n packageName: string;\n packageVersion: string;\n protocolVersion: 45;\n upgradeManagementVersion: number;\n controllerPid: number;\n processGroupId: number;\n processFingerprint: string;\n processMode: TControllerProcessMode;\n lifecycleState: TControllerLifecycleState;\n startedAt: number;\n setupRequired: boolean;\n /** Present when the controller runs as the systemd user unit (`agl service install`); absent for a detached or foreground one. */\n serviceUnit?: \"agl-controller.service\";\n /** The TLS mode of the runtime configuration the controller runs with; absent until it is resolved. */\n tlsMode?: TControllerTlsMode;\n upgrade?: IControllerUpgradeStatus;\n /** AGL's inference proxy, by state only; details need a signed-in owner (`controller.proxy.status.get`). */\n proxy?: { state: TControllerProxyState };\n harnesses: Array<{\n harnessId: TControllerSessionHarnessId;\n pid?: number;\n state: TControllerHarnessLifecycleState;\n healthy: boolean;\n version?: string;\n supportsLocalAttachments?: boolean;\n connectionMode?: 'local' | 'shared' | 'remote';\n diagnostic?: IControllerCodexDiagnostic;\n /** Codex only: automatic reconnection of this entry's profile to the local shared app-server. */\n rejoin?: IControllerCodexRejoinStatus;\n /**\n * Codex only, on the controller status: every connected or required profile. The entry's\n * `healthy` is then true only when each of them is, apart from a profile on an account Codex\n * cannot run on now, listed `stopped` with an `account_unavailable` diagnostic that says why;\n * its other members describe the default profile.\n */\n profiles?: IControllerCodexProfileStatus[];\n }>;\n}"
3890
+ "declaration": "interface IControllerStatus {\n packageName: string;\n packageVersion: string;\n protocolVersion: 46;\n upgradeManagementVersion: number;\n controllerPid: number;\n processGroupId: number;\n processFingerprint: string;\n processMode: TControllerProcessMode;\n lifecycleState: TControllerLifecycleState;\n startedAt: number;\n setupRequired: boolean;\n /** Present when the controller runs as the systemd user unit (`agl service install`); absent for a detached or foreground one. */\n serviceUnit?: \"agl-controller.service\";\n /** The TLS mode of the runtime configuration the controller runs with; absent until it is resolved. */\n tlsMode?: TControllerTlsMode;\n upgrade?: IControllerUpgradeStatus;\n /** AGL's inference proxy, by state only; details need a signed-in owner (`controller.proxy.status.get`). */\n proxy?: { state: TControllerProxyState };\n harnesses: Array<{\n harnessId: TControllerSessionHarnessId;\n pid?: number;\n state: TControllerHarnessLifecycleState;\n healthy: boolean;\n version?: string;\n supportsLocalAttachments?: boolean;\n connectionMode?: 'local' | 'shared' | 'remote';\n diagnostic?: IControllerCodexDiagnostic;\n /** Codex only: automatic reconnection of this entry's profile to the local shared app-server. */\n rejoin?: IControllerCodexRejoinStatus;\n /**\n * Codex only, on the controller status: every connected or required profile. The entry's\n * `healthy` is then true only when each of them is, apart from a profile on an account Codex\n * cannot run on now, listed `stopped` with an `account_unavailable` diagnostic that says why;\n * its other members describe the default profile.\n */\n profiles?: IControllerCodexProfileStatus[];\n }>;\n}"
3909
3891
  },
3910
3892
  {
3911
3893
  "name": "IControllerTerminalResource",
@@ -4743,10 +4725,55 @@ export const AGL_API_SURFACE: plugins.flexTypedRequest.IFlexTypedRequestSurface
4743
4725
  "name": "ITicketCreate",
4744
4726
  "declaration": "interface ITicketCreate {\n project: string;\n kind?: TTicketKind;\n title: string;\n description?: string;\n columnId?: TTicketColumn;\n parentId?: string;\n priority?: TTicketPriority;\n assignee?: ITicketPerson;\n labels?: string[];\n sourceText?: string;\n attachments?: ITicketAttachment[];\n}"
4745
4727
  },
4728
+ {
4729
+ "name": "ITicketDetail",
4730
+ "schema": {
4731
+ "type": "object",
4732
+ "properties": {
4733
+ "ticket": {
4734
+ "$ref": "#/$defs/ITicket"
4735
+ },
4736
+ "comments": {
4737
+ "type": "array",
4738
+ "items": {
4739
+ "$ref": "#/$defs/ITicketComment"
4740
+ }
4741
+ },
4742
+ "activity": {
4743
+ "type": "array",
4744
+ "items": {
4745
+ "$ref": "#/$defs/ITicketActivity"
4746
+ }
4747
+ },
4748
+ "questions": {
4749
+ "type": "array",
4750
+ "items": {
4751
+ "$ref": "#/$defs/ITicketQuestion"
4752
+ }
4753
+ },
4754
+ "historyBefore": {
4755
+ "type": "string"
4756
+ }
4757
+ },
4758
+ "required": [
4759
+ "ticket",
4760
+ "comments",
4761
+ "activity",
4762
+ "questions"
4763
+ ],
4764
+ "additionalProperties": false,
4765
+ "description": "A ticket, all its questions and one page of its history, oldest first. `activity` is the page; `comments` are the comments its `commented` entries name, whose `changes.comment` holds only `{ after: { id } }` here (the stored entry keeps the whole comment). `historyBefore` is present while older entries exist: pass it as `before`."
4766
+ },
4767
+ "declaration": "/**\n * A ticket, all its questions and one page of its history, oldest first. `activity` is the page; `comments` are the\n * comments its `commented` entries name, whose `changes.comment` holds only `{ after: { id } }` here (the stored\n * entry keeps the whole comment). `historyBefore` is present while older entries exist: pass it as `before`.\n */\ninterface ITicketDetail {\n ticket: ITicket;\n comments: ITicketComment[];\n activity: ITicketActivity[];\n questions: ITicketQuestion[];\n historyBefore?: string;\n}"
4768
+ },
4746
4769
  {
4747
4770
  "name": "ITicketFilters",
4748
4771
  "declaration": "interface ITicketFilters {\n mine?: boolean;\n project?: string;\n status?: TTicketColumn;\n /** Matches a hand label or an AutoLabel. */\n label?: string;\n text?: string;\n}"
4749
4772
  },
4773
+ {
4774
+ "name": "ITicketGet",
4775
+ "declaration": "/** A ticket and one page of its history: the newest entries, or those older than the entry `before` names, within `ticketHistoryPage`. */\ninterface ITicketGet {\n ticketId: string;\n /** An activity id, as `historyBefore` returned it: the page holds only older entries. */\n before?: string;\n /** Entries on the page, 1 to 500 (`ticketHistoryPage.maximum`); 50 (`ticketHistoryPage.default`) when absent. */\n limit?: number;\n /**\n * One comment of this ticket, by the id an activity entry or a fleet event names: the response holds the ticket, its\n * questions and that comment alone (`comments: [it]`, `activity: []`, no `historyBefore`). Excludes `before` and `limit`.\n */\n commentId?: string;\n}"
4776
+ },
4750
4777
  {
4751
4778
  "name": "ITicketMove",
4752
4779
  "declaration": "interface ITicketMove { ticketId: string; columnId: TTicketColumn; beforeTicketId?: string; }"
@@ -64,6 +64,11 @@ export interface IAGLHomePaths {
64
64
  proxy: string;
65
65
  /** The running proxy's private control socket. */
66
66
  proxySocket: string;
67
+ /**
68
+ * The proxy's confined listener: a Unix socket alone in a private directory of its own, which a confined run mounts
69
+ * into its container, so the container sees nothing else of AGL. Only confined credentials are accepted on it.
70
+ */
71
+ proxyConfinedSocket: string;
67
72
  /** The credentials the proxy keeps in files, one per client with delivery `file` (OpenCode's `{file:...}`). */
68
73
  proxyCredentials: string;
69
74
  /** The credential command a harness routed through the proxy runs; the proxy writes it whenever it starts. */
@@ -211,6 +216,7 @@ export const resolveAGLHomePaths = (
211
216
  claudeOnChatGpt: path.join(root, 'claude-gateway'),
212
217
  proxy: path.join(root, 'proxy'),
213
218
  proxySocket: path.join(sockets, 'agl-proxy.sock'),
219
+ proxyConfinedSocket: path.join(sockets, 'agl-proxy-confined', 'proxy.sock'),
214
220
  proxyCredentials: path.join(root, 'proxy', 'credentials'),
215
221
  proxyCredentialHelper: path.join(root, 'proxy', 'credential-helper'),
216
222
  migration: path.join(root, 'migration'),
@@ -3,6 +3,7 @@ import { DriverModel } from './driver/classes.driverstore.js';
3
3
  import { DirectReportModel } from './driver/classes.directreportstore.js';
4
4
  import { SessionFeedAcknowledgementModel } from './classes.sessionfeedacknowledgements.js';
5
5
  import { HarnessReleaseModel } from './classes.harnessreleases.js';
6
+ import { WorkerCallerBindingModel } from './classes.workercallerbindings.js';
6
7
  import { FleetTicketEffectModel } from './fleet/classes.ticketeffects.js';
7
8
  import { ControllerCodexOriginMigrationModel } from '../ts_migration/v29_codexorigins.js';
8
9
  import { ControllerCodexConnectionStore } from './classes.codexconnectionstore.js';
@@ -781,6 +782,7 @@ const setAuthStoreModelManager = (
781
782
  plugins.nosqldb.setDefaultManagerForDoc(managerArg, SessionFeedAcknowledgementModel);
782
783
  plugins.nosqldb.setDefaultManagerForDoc(managerArg, FleetTicketEffectModel);
783
784
  plugins.nosqldb.setDefaultManagerForDoc(managerArg, HarnessReleaseModel);
785
+ plugins.nosqldb.setDefaultManagerForDoc(managerArg, WorkerCallerBindingModel);
784
786
  plugins.nosqldb.setDefaultManagerForDoc(managerArg, TicketModel);
785
787
  plugins.nosqldb.setDefaultManagerForDoc(managerArg, TicketBoardModel);
786
788
  plugins.nosqldb.setDefaultManagerForDoc(managerArg, TicketCommentModel);
@@ -1054,6 +1056,8 @@ export class SmartDataAuthStore implements IAuthStore, IAuthStoreSessionIdentity
1054
1056
  await FleetTicketEffectModel.exact.prepare();
1055
1057
  await HarnessReleaseModel.init();
1056
1058
  await HarnessReleaseModel.exact.prepare();
1059
+ await WorkerCallerBindingModel.init();
1060
+ await WorkerCallerBindingModel.exact.prepare();
1057
1061
  await TicketModel.init();
1058
1062
  await TicketBoardModel.init();
1059
1063
  await TicketCommentModel.init();
@@ -5,6 +5,7 @@ import { FleetDispatchError } from './fleet/interfaces.workers.js';
5
5
  import type { TFleetWorkerAction, TFleetWorkerRequest } from '../ts_interfaces/fleetworkers.js';
6
6
  import { ControllerBrowserOperationAuthority } from './classes.browseroperationauthority.js';
7
7
  import { ControllerWorkerCallers, type IWorkerCodexCallerOrigin } from './classes.workercallers.js';
8
+ import { WorkerCallerBindingStore } from './classes.workercallerbindings.js';
8
9
  import { FleetWorkerLaunches } from './fleet/classes.workerlaunches.js';
9
10
  import { workerCallerEnrollmentToolName } from './classes.mcpworkercaller.js';
10
11
  import { readWorkerProcessSnapshot, readWorkerUnixSocketOwner, workerSnapshotsEqual, isWorkerCallerProofRefusal, type IWorkerCallerConnection } from './functions.workerprocessproof.js';
@@ -86,6 +87,7 @@ import {
86
87
  } from './functions.canonicaldirectory.js';
87
88
  import { commitinfo } from './00_commitinfo_data.js';
88
89
  import { controllerFailureJournal } from './classes.failurejournal.js';
90
+ import { ControllerEventDelivery } from './classes.controllereventdelivery.js';
89
91
  import {
90
92
  controllerPackageName,
91
93
  controllerBuiltinCommands,
@@ -928,6 +930,9 @@ const maxSessionArchiveReconciliationReads = 3;
928
930
  const maxLatestOpenCodeToolExecutions = 512;
929
931
  const maxLatestOpenCodeMessageUpdates = 512;
930
932
  const maxPendingFlexControllerEvents = 512;
933
+ /** Events and serialized bytes one browser may have waiting before its queue collapses into a resync. */
934
+ const maxPendingControllerEventsPerViewer = 512;
935
+ const maxPendingControllerEventBytesPerViewer = 16 * 1024 * 1024;
931
936
  const maxLatestFlexToolExecutions = 512;
932
937
  const maxLatestFlexToolExecutionBytes = 8 * 1024 * 1024;
933
938
  const maxLatestFlexMessageUpdateBytes = 16 * 1024 * 1024;
@@ -1351,6 +1356,12 @@ interface ILatestTextUpdate {
1351
1356
  bytes?: number;
1352
1357
  }
1353
1358
 
1359
+ /** A live Flex reasoning snapshot with the accounting its deltas extend. */
1360
+ type TLatestFlexReasoningUpdate = ILatestReasoningUpdate & IFlexLiveTextAccounting;
1361
+
1362
+ /** A live Flex text snapshot with the accounting its deltas extend. */
1363
+ type TLatestFlexTextUpdate = ILatestTextUpdate & IFlexLiveTextAccounting;
1364
+
1354
1365
  type TControllerMessageDelta = IControllerReasoningDelta | IControllerTextDelta;
1355
1366
 
1356
1367
  interface IControllerTextDeltaFragment {
@@ -1388,6 +1399,67 @@ const controllerTextAfterAppendUtf8Bytes = (
1388
1399
  return Number.isSafeInteger(result) && result >= 0 ? result : undefined;
1389
1400
  };
1390
1401
 
1402
+ const isHighSurrogate = (codeArg: number): boolean => codeArg >= 0xD800 && codeArg <= 0xDBFF;
1403
+ const isLowSurrogate = (codeArg: number): boolean => codeArg >= 0xDC00 && codeArg <= 0xDFFF;
1404
+
1405
+ /** The UTF-8 bytes `JSON.stringify` writes for a string's content (without its quotes), counted without serializing. */
1406
+ const controllerJsonStringContentBytes = (valueArg: string): number => {
1407
+ let bytes = 0;
1408
+ for (let index = 0; index < valueArg.length; index += 1) {
1409
+ const code = valueArg.charCodeAt(index);
1410
+ if (
1411
+ code === 0x22 || code === 0x5C
1412
+ || code === 0x08 || code === 0x09 || code === 0x0A || code === 0x0C || code === 0x0D
1413
+ ) bytes += 2;
1414
+ else if (code < 0x20) bytes += 6;
1415
+ else if (code < 0x80) bytes += 1;
1416
+ else if (code < 0x800) bytes += 2;
1417
+ else if (isHighSurrogate(code)) {
1418
+ if (index + 1 < valueArg.length && isLowSurrogate(valueArg.charCodeAt(index + 1))) {
1419
+ bytes += 4;
1420
+ index += 1;
1421
+ } else bytes += 6;
1422
+ } else if (isLowSurrogate(code)) bytes += 6;
1423
+ else bytes += 3;
1424
+ }
1425
+ return bytes;
1426
+ };
1427
+
1428
+ /**
1429
+ * What a live Flex snapshot's text costs, kept beside the text so a delta is accounted by its own length: the
1430
+ * cumulative text is never encoded, serialized or even read again on the append path.
1431
+ */
1432
+ interface IFlexLiveTextAccounting {
1433
+ textUtf8Bytes: number;
1434
+ /** `JSON.stringify(text)` bytes without the quotes. */
1435
+ textJsonBytes: number;
1436
+ textEndsWithHighSurrogate: boolean;
1437
+ }
1438
+
1439
+ const flexLiveTextAccounting = (textArg: string): IFlexLiveTextAccounting => ({
1440
+ textUtf8Bytes: Buffer.byteLength(textArg, 'utf8'),
1441
+ textJsonBytes: controllerJsonStringContentBytes(textArg),
1442
+ textEndsWithHighSurrogate: textArg.length > 0 && isHighSurrogate(textArg.charCodeAt(textArg.length - 1)),
1443
+ });
1444
+
1445
+ const flexLiveTextAccountingAfterAppend = (
1446
+ accountingArg: IFlexLiveTextAccounting,
1447
+ deltaArg: string,
1448
+ ): IFlexLiveTextAccounting | undefined => {
1449
+ if (deltaArg.length === 0) return accountingArg;
1450
+ // A surrogate pair split across the append: two lone halves (3 + 3 UTF-8 bytes, `\uXXXX` + `\uXXXX` in JSON)
1451
+ // become one pair (4 bytes in both).
1452
+ const joinsPair = accountingArg.textEndsWithHighSurrogate && isLowSurrogate(deltaArg.charCodeAt(0));
1453
+ const textUtf8Bytes = accountingArg.textUtf8Bytes + Buffer.byteLength(deltaArg, 'utf8') - (joinsPair ? 2 : 0);
1454
+ const textJsonBytes = accountingArg.textJsonBytes + controllerJsonStringContentBytes(deltaArg) - (joinsPair ? 8 : 0);
1455
+ if (!Number.isSafeInteger(textUtf8Bytes) || !Number.isSafeInteger(textJsonBytes)) return undefined;
1456
+ return {
1457
+ textUtf8Bytes,
1458
+ textJsonBytes,
1459
+ textEndsWithHighSurrogate: isHighSurrogate(deltaArg.charCodeAt(deltaArg.length - 1)),
1460
+ };
1461
+ };
1462
+
1391
1463
  const fragmentControllerTextDelta = (valueArg: string): IControllerTextDeltaFragment[] => {
1392
1464
  const fragments: IControllerTextDeltaFragment[] = [];
1393
1465
  let delta = '';
@@ -3030,8 +3102,8 @@ export class OpenCodeController {
3030
3102
  private readonly latestOpenCodeReasoningUpdates = new Map<string, ILatestReasoningUpdate>();
3031
3103
  private readonly latestOpenCodeTextUpdates = new Map<string, ILatestTextUpdate>();
3032
3104
  private readonly latestFlexToolExecutions = new Map<string, ILatestToolExecution>();
3033
- private readonly latestFlexReasoningUpdates = new Map<string, ILatestReasoningUpdate>();
3034
- private readonly latestFlexTextUpdates = new Map<string, ILatestTextUpdate>();
3105
+ private readonly latestFlexReasoningUpdates = new Map<string, TLatestFlexReasoningUpdate>();
3106
+ private readonly latestFlexTextUpdates = new Map<string, TLatestFlexTextUpdate>();
3035
3107
  private readonly pendingFlexControllerEvents = new Map<string, IControllerEvent>();
3036
3108
  private readonly pendingFlexAutoAcceptSweeps = new Map<string, IFlexAutoAcceptSweep>();
3037
3109
  private openCodeControllerEventDrainTask?: Promise<void>;
@@ -3048,6 +3120,7 @@ export class OpenCodeController {
3048
3120
  private pendingFlexToolEventBytes = 0;
3049
3121
  private pendingFlexMessageEventBytes = 0;
3050
3122
  private flexControllerEventDrainTask?: Promise<void>;
3123
+ private controllerEventDelivery?: ControllerEventDelivery;
3051
3124
  private flexAutoAcceptDrainTask?: Promise<void>;
3052
3125
  /** Sweeps whose auto-accept failed, queued again when the retry timer fires. */
3053
3126
  private readonly failedFlexAutoAcceptSweeps = new Map<string, IFlexAutoAcceptSweep>();
@@ -10555,7 +10628,12 @@ export class OpenCodeController {
10555
10628
  readMaster: async () => {
10556
10629
  const resource = await configurationAuthority.getResource();
10557
10630
  return (await configurationAuthority.getMaster({ resourceId: resource.resourceId })).master;
10558
- }, captureCodexOrigin: launch => this.captureWorkerCodexOrigin(launch) });
10631
+ }, captureCodexOrigin: launch => this.captureWorkerCodexOrigin(launch),
10632
+ // Claude launches keep their bindings, so a fleet worker's bridge proves itself again after this restart and
10633
+ // after Claude Code continued its job in another process; the fleet reads the job's inventory for that proof.
10634
+ bindings: new WorkerCallerBindingStore(),
10635
+ observeClaudeJob: async (request) => this.fleetWorkers
10636
+ ? await this.fleetWorkers.observeCallerJob(request) : { state: 'unproven', reason: 'the fleet is not running yet' } });
10559
10637
  this.workerCallers = workerCallers;
10560
10638
  const mcpControllerHost = new ControllerMcpHost({
10561
10639
  controllerPort: runtimeConfig.controllerPort,
@@ -12955,6 +13033,9 @@ export class OpenCodeController {
12955
13033
  }
12956
13034
  };
12957
13035
 
13036
+ // Fleet jobs prepare, bind and revoke worker launches through the caller hooks: admit none and let the running
13037
+ // ones settle before the hooks go, so a stop or archive meets the fleet's own refusal, never missing hooks.
13038
+ await stopPart(async () => { await this.fleetWorkers?.closeAdmission(); });
12958
13039
  await stopPart(async () => {
12959
13040
  await this.workerCallers?.close();
12960
13041
  this.workerCallers = undefined;
@@ -13221,6 +13302,7 @@ export class OpenCodeController {
13221
13302
  ...(this.openCodeControllerEventDrainTask ? [this.openCodeControllerEventDrainTask] : []),
13222
13303
  ...(this.flexControllerEventDrainTask ? [this.flexControllerEventDrainTask] : []),
13223
13304
  ]);
13305
+ await this.controllerEventDelivery?.stop();
13224
13306
 
13225
13307
  if (this.flexProjectionReader) {
13226
13308
  await stopPart(async () => {
@@ -16748,7 +16830,7 @@ export class OpenCodeController {
16748
16830
  return { owner, ...(configuration.boardPump ? { configuration: configuration.boardPump } : {}) };
16749
16831
  },
16750
16832
  tickets: async () => (await this.ticketStore.execute('list', {}, undefined)).tickets,
16751
- ticket: (ticketKeyArg) => this.ticketStore.execute('get', { ticketId: ticketKeyArg }, undefined),
16833
+ history: (ticketKeyArg) => this.ticketStore.history(ticketKeyArg),
16752
16834
  workers: (ownerArg) => this.requireFleetWorkers().views(ownerArg),
16753
16835
  steer: (sessionIdArg, ticketKeyArg, textArg) => this.requireFleetWorkers().notify(sessionIdArg, ticketKeyArg, textArg),
16754
16836
  publish: (eventArg, ownerArg) => this.requireFleetWorkers().events.publish(eventArg, ownerArg),
@@ -17622,6 +17704,7 @@ export class OpenCodeController {
17622
17704
  driver: {
17623
17705
  instructions: service.instructions(await this.readDriverConfiguration()),
17624
17706
  notes: state.scratchpad.text,
17707
+ notesRevision: state.scratchpad.revision,
17625
17708
  },
17626
17709
  };
17627
17710
  }
@@ -23842,7 +23925,8 @@ export class OpenCodeController {
23842
23925
  let cursor: string | undefined;
23843
23926
  for (let pages = 0; ; pages++) {
23844
23927
  if (pages >= 1000) throw new Error('Flex delivery transcript exceeded its inspection limit.');
23845
- const page = await supervisorArg.request('message.page', {
23928
+ // Metadata pages: identity and state only, so a full scan never moves the transcript's text.
23929
+ const page = await supervisorArg.request('message.metadata.page', {
23846
23930
  ...payload, limit: 50, includeUndone: true, ...(cursor === undefined ? {} : { before: cursor }),
23847
23931
  }, signal);
23848
23932
  for (const message of page.messages) {
@@ -29179,7 +29263,7 @@ export class OpenCodeController {
29179
29263
 
29180
29264
  private setLatestFlexReasoningUpdate(
29181
29265
  keyArg: string,
29182
- entryArg: ILatestReasoningUpdate,
29266
+ entryArg: TLatestFlexReasoningUpdate,
29183
29267
  bytesArg: number,
29184
29268
  ): void {
29185
29269
  this.deleteLatestFlexReasoningUpdate(keyArg);
@@ -29189,7 +29273,7 @@ export class OpenCodeController {
29189
29273
 
29190
29274
  private setLatestFlexTextUpdate(
29191
29275
  keyArg: string,
29192
- entryArg: ILatestTextUpdate,
29276
+ entryArg: TLatestFlexTextUpdate,
29193
29277
  bytesArg: number,
29194
29278
  ): void {
29195
29279
  this.deleteLatestFlexTextUpdate(keyArg);
@@ -29712,7 +29796,7 @@ export class OpenCodeController {
29712
29796
  {
29713
29797
  projectId: eventArg.scopeId,
29714
29798
  update,
29715
- textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
29799
+ ...flexLiveTextAccounting(update.text),
29716
29800
  },
29717
29801
  eventBytes,
29718
29802
  );
@@ -29779,7 +29863,7 @@ export class OpenCodeController {
29779
29863
  {
29780
29864
  projectId: eventArg.scopeId,
29781
29865
  update,
29782
- textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
29866
+ ...flexLiveTextAccounting(update.text),
29783
29867
  },
29784
29868
  eventBytes,
29785
29869
  );
@@ -29827,13 +29911,11 @@ export class OpenCodeController {
29827
29911
  this.advanceFlexMessageStreamEpoch(true);
29828
29912
  return;
29829
29913
  }
29830
- const textUtf8Bytes = controllerTextAfterAppendUtf8Bytes(
29831
- previous.text,
29832
- entry.textUtf8Bytes,
29833
- eventArg.delta,
29834
- );
29914
+ const accounting = flexLiveTextAccountingAfterAppend(entry, eventArg.delta);
29915
+ const textUtf8Bytes = accounting?.textUtf8Bytes;
29835
29916
  if (
29836
- textUtf8Bytes === undefined
29917
+ accounting === undefined
29918
+ || textUtf8Bytes === undefined
29837
29919
  || textUtf8Bytes <= entry.textUtf8Bytes
29838
29920
  || textUtf8Bytes !== eventArg.textUtf8Bytes
29839
29921
  ) {
@@ -29842,9 +29924,11 @@ export class OpenCodeController {
29842
29924
  }
29843
29925
 
29844
29926
  const revision = this.flexMessageStreamRevision + 1;
29927
+ // Measured with empty text first: the snapshot's size is that frame plus the text's tracked JSON bytes, so
29928
+ // the growing text is serialized only when a snapshot is served.
29845
29929
  const update = {
29846
29930
  ...previous,
29847
- text: `${previous.text}${eventArg.delta}`,
29931
+ text: '',
29848
29932
  sourceUpdatedAt,
29849
29933
  revision,
29850
29934
  streamEpoch: this.flexMessageStreamEpoch,
@@ -29906,7 +29990,7 @@ export class OpenCodeController {
29906
29990
  timestamp: sourceUpdatedAt,
29907
29991
  };
29908
29992
  const eventBytes = Buffer.byteLength(JSON.stringify(controllerEvent), 'utf8');
29909
- const snapshotBytes = Buffer.byteLength(JSON.stringify(snapshotEvent), 'utf8');
29993
+ const snapshotBytes = Buffer.byteLength(JSON.stringify(snapshotEvent), 'utf8') + accounting.textJsonBytes;
29910
29994
  if (
29911
29995
  eventBytes > controllerMaxLiveMessageEventBytes
29912
29996
  || snapshotBytes > controllerMaxLiveMessageEventBytes
@@ -29917,6 +30001,7 @@ export class OpenCodeController {
29917
30001
  return;
29918
30002
  }
29919
30003
 
30004
+ update.text = `${previous.text}${eventArg.delta}`;
29920
30005
  this.flexMessageStreamRevision = revision;
29921
30006
  if (eventArg.partType === 'reasoning') {
29922
30007
  this.setLatestFlexReasoningUpdate(
@@ -29924,7 +30009,7 @@ export class OpenCodeController {
29924
30009
  {
29925
30010
  projectId: eventArg.scopeId,
29926
30011
  update: update as IControllerReasoningUpdate,
29927
- textUtf8Bytes,
30012
+ ...accounting,
29928
30013
  },
29929
30014
  snapshotBytes,
29930
30015
  );
@@ -29934,7 +30019,7 @@ export class OpenCodeController {
29934
30019
  {
29935
30020
  projectId: eventArg.scopeId,
29936
30021
  update: update as IControllerTextUpdate,
29937
- textUtf8Bytes,
30022
+ ...accounting,
29938
30023
  },
29939
30024
  snapshotBytes,
29940
30025
  );
@@ -30625,7 +30710,7 @@ export class OpenCodeController {
30625
30710
  {
30626
30711
  projectId: eventArg.projectId,
30627
30712
  update,
30628
- textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
30713
+ ...flexLiveTextAccounting(update.text),
30629
30714
  },
30630
30715
  Buffer.byteLength(JSON.stringify(event), 'utf8'),
30631
30716
  );
@@ -30653,7 +30738,7 @@ export class OpenCodeController {
30653
30738
  {
30654
30739
  projectId: eventArg.projectId,
30655
30740
  update,
30656
- textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
30741
+ ...flexLiveTextAccounting(update.text),
30657
30742
  },
30658
30743
  Buffer.byteLength(JSON.stringify(event), 'utf8'),
30659
30744
  );
@@ -35065,20 +35150,44 @@ export class OpenCodeController {
35065
35150
  ));
35066
35151
  }
35067
35152
 
35153
+ /** Every browser's own ordered event queue (`ControllerEventDelivery`), created with the first event. */
35154
+ private controllerEventDeliveryQueues(): ControllerEventDelivery {
35155
+ this.controllerEventDelivery ??= new ControllerEventDelivery({
35156
+ viewers: async () => {
35157
+ const typedSocket = this.typedServer?.typedsocket;
35158
+ if (!typedSocket) return [];
35159
+ const targets = await this.controllerEventViewers();
35160
+ return targets.map((targetArg) => ({
35161
+ peerId: targetArg.peer.id,
35162
+ send: async (eventArg: IControllerEvent) => {
35163
+ await typedSocket.createTypedRequest<IReq_ControllerEvent>(
35164
+ 'controller.event',
35165
+ targetArg,
35166
+ { timeoutMs: 2_000 },
35167
+ ).fire(eventArg, { timeoutMs: 2_000, maxRetries: 0 });
35168
+ },
35169
+ }));
35170
+ },
35171
+ eventKey: (eventArg) => this.controllerEventKey(eventArg),
35172
+ mergeDelta: mergeControllerMessageDeltaEvents,
35173
+ limits: {
35174
+ events: maxPendingControllerEventsPerViewer,
35175
+ bytes: maxPendingControllerEventBytesPerViewer,
35176
+ mergedDeltaBytes: controllerMaxLiveMessageEventBytes,
35177
+ },
35178
+ });
35179
+ return this.controllerEventDelivery;
35180
+ }
35181
+
35182
+ /**
35183
+ * The driver and the session feed see every event once, in order, before any browser; each browser then gets it
35184
+ * through its own queue, so one that acknowledges slowly delays only itself. Resolves once every browser's queue
35185
+ * holds the event, never on an acknowledgement.
35186
+ */
35068
35187
  private async pushControllerEvent(eventArg: IControllerEvent): Promise<void> {
35069
35188
  this.driverService?.observe(eventArg);
35070
35189
  this.sessionFeed?.observe(eventArg);
35071
- const typedSocket = this.typedServer?.typedsocket;
35072
- if (!typedSocket) return;
35073
- const targets = await this.controllerEventViewers();
35074
- await Promise.allSettled(targets.map(async (targetArg) => {
35075
- const request = typedSocket.createTypedRequest<IReq_ControllerEvent>(
35076
- 'controller.event',
35077
- targetArg,
35078
- { timeoutMs: 2_000 },
35079
- );
35080
- await request.fire(eventArg, { timeoutMs: 2_000, maxRetries: 0 });
35081
- }));
35190
+ await this.controllerEventDeliveryQueues().push(eventArg);
35082
35191
  }
35083
35192
 
35084
35193
  private runUpgradePreparationAdmission<T>(operationArg: () => Promise<T>): Promise<T> {