@forgeax/engine-intelligence 0.1.7 → 0.1.20

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.
@@ -4,7 +4,9 @@ import { createIntelligenceRuntime } from '../runtime';
4
4
  import {
5
5
  bindIntelligencePort,
6
6
  createIntelligencePortClient,
7
+ type IntelligenceHostCommand,
7
8
  type IntelligenceMessagePort,
9
+ type IntelligenceRealmMessage,
8
10
  } from '../transport';
9
11
  import { type ActivitySink, activityId, type IntelligenceProvider } from '../types';
10
12
 
@@ -765,6 +767,681 @@ describe('intelligence MessagePort boundary', () => {
765
767
  }
766
768
  });
767
769
 
770
+ it('contains a synchronous Worker client close command failure and releases local transport', async () => {
771
+ let starts = 0;
772
+ let closeCalls = 0;
773
+ const provider: IntelligenceProvider = {
774
+ id: 'port.client-close-command.provider',
775
+ start() {
776
+ starts += 1;
777
+ return ok(undefined);
778
+ },
779
+ cancel() {
780
+ return ok(undefined);
781
+ },
782
+ async close() {
783
+ closeCalls += 1;
784
+ },
785
+ };
786
+ const runtime = createIntelligenceRuntime(provider);
787
+ const channel = new MessageChannel();
788
+ const rawClientPort = channel.port2;
789
+ const commandKinds: string[] = [];
790
+ let removeListenerCalls = 0;
791
+ let clientPortCloseCalls = 0;
792
+ const clientPort: IntelligenceMessagePort = {
793
+ postMessage(message) {
794
+ commandKinds.push(message.kind);
795
+ if (message.kind === 'intelligence-close') {
796
+ throw new Error('sentinel Worker client close command failure');
797
+ }
798
+ rawClientPort.postMessage(message);
799
+ },
800
+ addEventListener(type, listener) {
801
+ rawClientPort.addEventListener(type, listener as EventListener);
802
+ },
803
+ removeEventListener(type, listener) {
804
+ removeListenerCalls += 1;
805
+ rawClientPort.removeEventListener(type, listener as EventListener);
806
+ },
807
+ start() {
808
+ rawClientPort.start();
809
+ },
810
+ close() {
811
+ clientPortCloseCalls += 1;
812
+ rawClientPort.close();
813
+ },
814
+ };
815
+ const host = bindIntelligencePort(channel.port1 as unknown as IntelligenceMessagePort, runtime);
816
+ const client = createIntelligencePortClient(provider.id, clientPort, {
817
+ createActivityId: () => activityId('client-close-command-activity'),
818
+ createSessionId: () => 'client-close-command-session',
819
+ });
820
+
821
+ try {
822
+ const started = client.submit({ input: 'live close' });
823
+ expect(started.ok).toBe(true);
824
+ if (!started.ok) return;
825
+ for (let attempt = 0; attempt < 20 && starts === 0; attempt += 1) {
826
+ await new Promise((resolve) => setTimeout(resolve, 5));
827
+ }
828
+ expect(starts).toBe(1);
829
+
830
+ client.poll();
831
+ const closeTask = client.close();
832
+ expect(client.close()).toBe(closeTask);
833
+ await expect(closeTask).resolves.toBeUndefined();
834
+
835
+ expect(commandKinds).toEqual([
836
+ 'intelligence-submit',
837
+ 'intelligence-poll',
838
+ 'intelligence-close',
839
+ ]);
840
+ expect(removeListenerCalls).toBe(1);
841
+ expect(clientPortCloseCalls).toBe(1);
842
+ expect(closeCalls).toBe(0);
843
+
844
+ const afterCloseSubmit = client.submit({ input: 'after close' });
845
+ expect(afterCloseSubmit.ok).toBe(false);
846
+ if (!afterCloseSubmit.ok) expect(afterCloseSubmit.error.code).toBe('intelligence-closed');
847
+ const afterCloseCancel = client.cancel(started.value.id);
848
+ expect(afterCloseCancel.ok).toBe(false);
849
+ if (!afterCloseCancel.ok) expect(afterCloseCancel.error.code).toBe('intelligence-closed');
850
+ expect(client.poll()).toEqual([]);
851
+
852
+ await expect(host.close()).resolves.toBeUndefined();
853
+ expect(closeCalls).toBe(1);
854
+ } finally {
855
+ rawClientPort.close();
856
+ await host.close().catch(() => undefined);
857
+ }
858
+ });
859
+
860
+ it('contains a synchronous Worker client submit command failure and clears local state', async () => {
861
+ const sinks = new Map<string, ActivitySink>();
862
+ const starts: string[] = [];
863
+ let closeCalls = 0;
864
+ const provider: IntelligenceProvider = {
865
+ id: 'port.client-submit-command.provider',
866
+ start(submission, sink) {
867
+ starts.push(submission.input);
868
+ sinks.set(String(submission.id), sink);
869
+ if (submission.input === 'live') sink.text('pending');
870
+ return ok(undefined);
871
+ },
872
+ cancel(id) {
873
+ sinks.get(String(id))?.cancelled();
874
+ return ok(undefined);
875
+ },
876
+ async close() {
877
+ closeCalls += 1;
878
+ },
879
+ };
880
+ const runtime = createIntelligenceRuntime(provider, {
881
+ limits: { maxConcurrentActivities: 1 },
882
+ });
883
+ const channel = new MessageChannel();
884
+ const rawClientPort = channel.port2;
885
+ const commandKinds: string[] = [];
886
+ let removeListenerCalls = 0;
887
+ let clientPortCloseCalls = 0;
888
+ const clientPort: IntelligenceMessagePort = {
889
+ postMessage(message) {
890
+ commandKinds.push(message.kind);
891
+ if (message.kind === 'intelligence-submit' && message.submission.input === 'submit-fault') {
892
+ throw new Error('sentinel Worker client submit command failure');
893
+ }
894
+ rawClientPort.postMessage(message);
895
+ },
896
+ addEventListener(type, listener) {
897
+ rawClientPort.addEventListener(type, listener as EventListener);
898
+ },
899
+ removeEventListener(type, listener) {
900
+ removeListenerCalls += 1;
901
+ rawClientPort.removeEventListener(type, listener as EventListener);
902
+ },
903
+ start() {
904
+ rawClientPort.start();
905
+ },
906
+ close() {
907
+ clientPortCloseCalls += 1;
908
+ rawClientPort.close();
909
+ },
910
+ };
911
+ const host = bindIntelligencePort(channel.port1 as unknown as IntelligenceMessagePort, runtime);
912
+ const client = createIntelligencePortClient(provider.id, clientPort, {
913
+ limits: { maxConcurrentActivities: 2 },
914
+ createActivityId: (() => {
915
+ let identity = 0;
916
+ return () => activityId(`client-submit-command-${++identity}`);
917
+ })(),
918
+ createSessionId: () => 'client-submit-command-session',
919
+ });
920
+
921
+ try {
922
+ const live = client.submit({ input: 'live' });
923
+ expect(live.ok).toBe(true);
924
+ const hostRejected = client.submit({ input: 'host-reject' });
925
+ expect(hostRejected.ok).toBe(true);
926
+ if (!live.ok || !hostRejected.ok) return;
927
+
928
+ let rejectionObserved = false;
929
+ const rejectionListener = (event: MessageEvent<unknown>): void => {
930
+ const value = event.data as { readonly kind?: unknown; readonly activityId?: unknown };
931
+ if (value.kind === 'intelligence-rejected' && value.activityId === hostRejected.value.id) {
932
+ rejectionObserved = true;
933
+ }
934
+ };
935
+ rawClientPort.addEventListener('message', rejectionListener);
936
+ rawClientPort.start();
937
+ for (let attempt = 0; attempt < 40 && !rejectionObserved; attempt += 1) {
938
+ await new Promise((resolve) => setTimeout(resolve, 1));
939
+ }
940
+ rawClientPort.removeEventListener('message', rejectionListener);
941
+ expect(rejectionObserved).toBe(true);
942
+
943
+ client.poll();
944
+ for (
945
+ let attempt = 0;
946
+ attempt < 40 && !commandKinds.includes('intelligence-poll');
947
+ attempt += 1
948
+ ) {
949
+ await new Promise((resolve) => setTimeout(resolve, 1));
950
+ }
951
+ const failed = client.submit({ input: 'submit-fault' });
952
+ expect(failed.ok).toBe(false);
953
+ if (!failed.ok) expect(failed.error.code).toBe('intelligence-closed');
954
+
955
+ const commandCountAfterFailure = commandKinds.length;
956
+ expect(client.poll()).toEqual([]);
957
+ const afterFailure = client.submit({ input: 'after-failure' });
958
+ expect(afterFailure.ok).toBe(false);
959
+ if (!afterFailure.ok) expect(afterFailure.error.code).toBe('intelligence-closed');
960
+ const cancelAfterFailure = client.cancel(live.value.id);
961
+ expect(cancelAfterFailure.ok).toBe(false);
962
+ if (!cancelAfterFailure.ok) expect(cancelAfterFailure.error.code).toBe('intelligence-closed');
963
+ expect(commandKinds).toHaveLength(commandCountAfterFailure);
964
+ expect(starts).toEqual(['live']);
965
+ expect(removeListenerCalls).toBe(1);
966
+ expect(clientPortCloseCalls).toBe(1);
967
+
968
+ const closeTask = client.close();
969
+ expect(client.close()).toBe(closeTask);
970
+ await expect(closeTask).resolves.toBeUndefined();
971
+ await expect(host.close()).resolves.toBeUndefined();
972
+ expect(closeCalls).toBe(1);
973
+ sinks.get(String(live.value.id))?.complete('late');
974
+ expect(runtime.poll()).toEqual([]);
975
+ } finally {
976
+ rawClientPort.close();
977
+ await host.close().catch(() => undefined);
978
+ }
979
+ });
980
+
981
+ it('contains a synchronous Worker client cancel command failure and releases local transport', async () => {
982
+ const sinks = new Map<string, ActivitySink>();
983
+ let starts = 0;
984
+ let cancelCalls = 0;
985
+ let closeCalls = 0;
986
+ const provider: IntelligenceProvider = {
987
+ id: 'port.client-cancel-command.provider',
988
+ start(submission, sink) {
989
+ starts += 1;
990
+ sinks.set(String(submission.id), sink);
991
+ sink.text('staged-before-cancel-fault');
992
+ return ok(undefined);
993
+ },
994
+ cancel(id) {
995
+ cancelCalls += 1;
996
+ sinks.get(String(id))?.cancelled();
997
+ return ok(undefined);
998
+ },
999
+ async close() {
1000
+ closeCalls += 1;
1001
+ },
1002
+ };
1003
+ const runtime = createIntelligenceRuntime(provider, {
1004
+ limits: { maxConcurrentActivities: 1, maxPollEvents: 1 },
1005
+ });
1006
+ const channel = new MessageChannel();
1007
+ const rawHostPort = channel.port1;
1008
+ const rawClientPort = channel.port2;
1009
+ const clientCommands: string[] = [];
1010
+ const hostCommands: string[] = [];
1011
+ let stagedEventObserved = false;
1012
+ let removeListenerCalls = 0;
1013
+ let clientPortCloseCalls = 0;
1014
+ rawHostPort.addEventListener('message', (event: MessageEvent<unknown>) => {
1015
+ const message = event.data as { readonly kind?: unknown };
1016
+ if (typeof message.kind === 'string') hostCommands.push(message.kind);
1017
+ });
1018
+ rawHostPort.start();
1019
+ rawClientPort.addEventListener('message', (event: MessageEvent<unknown>) => {
1020
+ const message = event.data as {
1021
+ readonly kind?: unknown;
1022
+ readonly events?: readonly unknown[];
1023
+ };
1024
+ if (message.kind === 'intelligence-events' && (message.events?.length ?? 0) > 0) {
1025
+ stagedEventObserved = true;
1026
+ }
1027
+ });
1028
+ rawClientPort.start();
1029
+ const clientPort: IntelligenceMessagePort = {
1030
+ postMessage(message) {
1031
+ clientCommands.push(message.kind);
1032
+ if (message.kind === 'intelligence-cancel') {
1033
+ throw new Error('sentinel Worker client cancel command failure');
1034
+ }
1035
+ rawClientPort.postMessage(message);
1036
+ },
1037
+ addEventListener(type, listener) {
1038
+ rawClientPort.addEventListener(type, listener as EventListener);
1039
+ },
1040
+ removeEventListener(type, listener) {
1041
+ removeListenerCalls += 1;
1042
+ rawClientPort.removeEventListener(type, listener as EventListener);
1043
+ },
1044
+ start() {
1045
+ rawClientPort.start();
1046
+ },
1047
+ close() {
1048
+ clientPortCloseCalls += 1;
1049
+ rawClientPort.close();
1050
+ },
1051
+ };
1052
+ const host = bindIntelligencePort(rawHostPort as unknown as IntelligenceMessagePort, runtime);
1053
+ let activityIdentityCalls = 0;
1054
+ let sessionIdentityCalls = 0;
1055
+ const client = createIntelligencePortClient(provider.id, clientPort, {
1056
+ limits: { maxConcurrentActivities: 1, maxPollEvents: 1 },
1057
+ createActivityId: () => activityId(`client-cancel-command-${++activityIdentityCalls}`),
1058
+ createSessionId: () => `client-cancel-command-session-${++sessionIdentityCalls}`,
1059
+ });
1060
+
1061
+ try {
1062
+ const started = client.submit({ input: 'live-cancel' });
1063
+ expect(started.ok).toBe(true);
1064
+ if (!started.ok) return;
1065
+ for (let attempt = 0; attempt < 40 && starts === 0; attempt += 1) {
1066
+ await new Promise((resolve) => setTimeout(resolve, 1));
1067
+ }
1068
+ expect(starts).toBe(1);
1069
+
1070
+ expect(client.poll()).toEqual([]);
1071
+ for (let attempt = 0; attempt < 40 && !stagedEventObserved; attempt += 1) {
1072
+ await new Promise((resolve) => setTimeout(resolve, 1));
1073
+ }
1074
+ expect(stagedEventObserved).toBe(true);
1075
+
1076
+ let cancellation: ReturnType<typeof client.cancel> | undefined;
1077
+ let thrown: unknown;
1078
+ try {
1079
+ cancellation = client.cancel(started.value.id);
1080
+ } catch (cause) {
1081
+ thrown = cause;
1082
+ }
1083
+ expect(thrown).toBeUndefined();
1084
+ expect(cancellation?.ok).toBe(false);
1085
+ if (cancellation !== undefined && !cancellation.ok) {
1086
+ expect(cancellation.error.code).toBe('intelligence-closed');
1087
+ }
1088
+ expect(clientCommands).toEqual([
1089
+ 'intelligence-submit',
1090
+ 'intelligence-poll',
1091
+ 'intelligence-cancel',
1092
+ ]);
1093
+ expect(hostCommands).toEqual(['intelligence-submit', 'intelligence-poll']);
1094
+ expect(cancelCalls).toBe(0);
1095
+ expect(removeListenerCalls).toBe(1);
1096
+ expect(clientPortCloseCalls).toBe(1);
1097
+
1098
+ const commandCountAfterFault = clientCommands.length;
1099
+ expect(client.submit({ input: 'after-cancel-fault' }).ok).toBe(false);
1100
+ expect(client.cancel(started.value.id).ok).toBe(false);
1101
+ expect(client.poll()).toEqual([]);
1102
+ expect(clientCommands).toHaveLength(commandCountAfterFault);
1103
+ expect(activityIdentityCalls).toBe(1);
1104
+ expect(sessionIdentityCalls).toBe(1);
1105
+
1106
+ await expect(host.close()).resolves.toBeUndefined();
1107
+ expect(closeCalls).toBe(1);
1108
+ sinks.get(String(started.value.id))?.text('late');
1109
+ sinks.get(String(started.value.id))?.complete('late');
1110
+ expect(client.poll()).toEqual([]);
1111
+ } finally {
1112
+ rawClientPort.close();
1113
+ await host.close().catch(() => undefined);
1114
+ }
1115
+ });
1116
+
1117
+ it('contains a synchronous Worker client poll command failure and discards staged events', async () => {
1118
+ const sinks = new Map<string, ActivitySink>();
1119
+ const starts: string[] = [];
1120
+ let closeCalls = 0;
1121
+ const provider: IntelligenceProvider = {
1122
+ id: 'port.client-poll-command.provider',
1123
+ start(submission, sink) {
1124
+ starts.push(submission.input);
1125
+ sinks.set(String(submission.id), sink);
1126
+ sink.text('staged-before-poll-fault');
1127
+ return ok(undefined);
1128
+ },
1129
+ cancel(id) {
1130
+ sinks.get(String(id))?.cancelled();
1131
+ return ok(undefined);
1132
+ },
1133
+ async close() {
1134
+ closeCalls += 1;
1135
+ },
1136
+ };
1137
+ const runtime = createIntelligenceRuntime(provider, {
1138
+ limits: { maxConcurrentActivities: 1, maxPollEvents: 1 },
1139
+ });
1140
+ const channel = new MessageChannel();
1141
+ const rawHostPort = channel.port1;
1142
+ const rawClientPort = channel.port2;
1143
+ const commandKinds: string[] = [];
1144
+ let pollRequests = 0;
1145
+ let normalPollResponses = 0;
1146
+ let stagedEventObserved = false;
1147
+ let throwNextPoll = false;
1148
+ let pollFaultThrows = 0;
1149
+ let removeListenerCalls = 0;
1150
+ let clientPortCloseCalls = 0;
1151
+ rawHostPort.addEventListener('message', (event: MessageEvent<unknown>) => {
1152
+ const message = event.data as { readonly kind?: unknown };
1153
+ if (message.kind === 'intelligence-poll') pollRequests += 1;
1154
+ });
1155
+ rawHostPort.start();
1156
+ rawClientPort.addEventListener('message', (event: MessageEvent<unknown>) => {
1157
+ const message = event.data as {
1158
+ readonly kind?: unknown;
1159
+ readonly events?: readonly unknown[];
1160
+ };
1161
+ if (message.kind === 'intelligence-events') {
1162
+ normalPollResponses += 1;
1163
+ if ((message.events?.length ?? 0) > 0) {
1164
+ stagedEventObserved = true;
1165
+ throwNextPoll = true;
1166
+ }
1167
+ }
1168
+ });
1169
+ rawClientPort.start();
1170
+ const clientPort: IntelligenceMessagePort = {
1171
+ postMessage(message) {
1172
+ commandKinds.push(message.kind);
1173
+ if (message.kind === 'intelligence-poll' && throwNextPoll) {
1174
+ throwNextPoll = false;
1175
+ pollFaultThrows += 1;
1176
+ throw new Error('sentinel Worker client poll command failure');
1177
+ }
1178
+ rawClientPort.postMessage(message);
1179
+ },
1180
+ addEventListener(type, listener) {
1181
+ rawClientPort.addEventListener(type, listener as EventListener);
1182
+ },
1183
+ removeEventListener(type, listener) {
1184
+ removeListenerCalls += 1;
1185
+ rawClientPort.removeEventListener(type, listener as EventListener);
1186
+ },
1187
+ start() {
1188
+ rawClientPort.start();
1189
+ },
1190
+ close() {
1191
+ clientPortCloseCalls += 1;
1192
+ rawClientPort.close();
1193
+ },
1194
+ };
1195
+ const host = bindIntelligencePort(rawHostPort as unknown as IntelligenceMessagePort, runtime);
1196
+ const client = createIntelligencePortClient(provider.id, clientPort, {
1197
+ limits: { maxConcurrentActivities: 1, maxPollEvents: 1 },
1198
+ createActivityId: (() => {
1199
+ let identity = 0;
1200
+ return () => activityId(`client-poll-command-${++identity}`);
1201
+ })(),
1202
+ createSessionId: () => 'client-poll-command-session',
1203
+ });
1204
+
1205
+ try {
1206
+ const started = client.submit({ input: 'live' });
1207
+ expect(started.ok).toBe(true);
1208
+ if (!started.ok) return;
1209
+ for (let attempt = 0; attempt < 40 && starts.length === 0; attempt += 1) {
1210
+ await new Promise((resolve) => setTimeout(resolve, 1));
1211
+ }
1212
+ expect(starts).toEqual(['live']);
1213
+
1214
+ expect(client.poll()).toEqual([]);
1215
+ for (let attempt = 0; attempt < 40 && !stagedEventObserved; attempt += 1) {
1216
+ await new Promise((resolve) => setTimeout(resolve, 1));
1217
+ }
1218
+ expect(normalPollResponses).toBe(1);
1219
+ expect(stagedEventObserved).toBe(true);
1220
+ expect(pollRequests).toBe(1);
1221
+
1222
+ const failedPoll = client.poll();
1223
+ expect(failedPoll).toEqual([]);
1224
+ expect(pollFaultThrows).toBe(1);
1225
+ expect(commandKinds).toEqual([
1226
+ 'intelligence-submit',
1227
+ 'intelligence-poll',
1228
+ 'intelligence-poll',
1229
+ ]);
1230
+ expect(pollRequests).toBe(1);
1231
+
1232
+ expect(client.poll()).toEqual([]);
1233
+ const afterFailure = client.submit({ input: 'after-failure' });
1234
+ expect(afterFailure.ok).toBe(false);
1235
+ if (!afterFailure.ok) expect(afterFailure.error.code).toBe('intelligence-closed');
1236
+ const cancelAfterFailure = client.cancel(started.value.id);
1237
+ expect(cancelAfterFailure.ok).toBe(false);
1238
+ if (!cancelAfterFailure.ok) expect(cancelAfterFailure.error.code).toBe('intelligence-closed');
1239
+ expect(commandKinds).toHaveLength(3);
1240
+ expect(starts).toEqual(['live']);
1241
+ expect(removeListenerCalls).toBe(1);
1242
+ expect(clientPortCloseCalls).toBe(1);
1243
+
1244
+ const closeTask = client.close();
1245
+ expect(client.close()).toBe(closeTask);
1246
+ await expect(closeTask).resolves.toBeUndefined();
1247
+ await expect(host.close()).resolves.toBeUndefined();
1248
+ expect(closeCalls).toBe(1);
1249
+ sinks.get(String(started.value.id))?.complete('late');
1250
+ expect(runtime.poll()).toEqual([]);
1251
+ } finally {
1252
+ rawClientPort.close();
1253
+ await host.close().catch(() => undefined);
1254
+ }
1255
+ });
1256
+
1257
+ it('contains a synchronous Host close notification failure and releases for a fresh binding', async () => {
1258
+ let sink: ActivitySink | undefined;
1259
+ let startCalls = 0;
1260
+ let closeCalls = 0;
1261
+ let portCloseCalls = 0;
1262
+ const provider: IntelligenceProvider = {
1263
+ id: 'port.close.notification-failure.provider',
1264
+ start(_submission, nextSink) {
1265
+ startCalls += 1;
1266
+ sink = nextSink;
1267
+ return ok(undefined);
1268
+ },
1269
+ cancel() {
1270
+ return ok(undefined);
1271
+ },
1272
+ async close() {
1273
+ closeCalls += 1;
1274
+ },
1275
+ };
1276
+ const runtime = createIntelligenceRuntime(provider);
1277
+ const channel = new MessageChannel();
1278
+ const rawHostPort = channel.port1;
1279
+ const rawClientPort = channel.port2;
1280
+ const hostPort: IntelligenceMessagePort = {
1281
+ postMessage(message) {
1282
+ if (message.kind === 'intelligence-closed') {
1283
+ throw new Error('sentinel Host close notification failure');
1284
+ }
1285
+ rawHostPort.postMessage(message);
1286
+ },
1287
+ addEventListener(type, listener) {
1288
+ rawHostPort.addEventListener(type, listener as EventListener);
1289
+ },
1290
+ removeEventListener(type, listener) {
1291
+ rawHostPort.removeEventListener(type, listener as EventListener);
1292
+ },
1293
+ start() {
1294
+ rawHostPort.start();
1295
+ },
1296
+ close() {
1297
+ portCloseCalls += 1;
1298
+ rawHostPort.close();
1299
+ },
1300
+ };
1301
+ const host = bindIntelligencePort(hostPort, runtime);
1302
+ const client = createIntelligencePortClient(
1303
+ provider.id,
1304
+ rawClientPort as unknown as IntelligenceMessagePort,
1305
+ );
1306
+
1307
+ try {
1308
+ const started = client.submit({ input: 'live close' });
1309
+ expect(started.ok).toBe(true);
1310
+ client.poll();
1311
+ for (let attempt = 0; attempt < 20 && startCalls === 0; attempt += 1) {
1312
+ await new Promise((resolve) => setTimeout(resolve, 5));
1313
+ }
1314
+ expect(startCalls).toBe(1);
1315
+
1316
+ await expect(host.close()).resolves.toBeUndefined();
1317
+ expect(closeCalls).toBe(1);
1318
+ expect(portCloseCalls).toBe(1);
1319
+
1320
+ sink?.text('late');
1321
+ sink?.complete('late');
1322
+ expect(runtime.poll()).toEqual([]);
1323
+
1324
+ const freshChannel = new MessageChannel();
1325
+ let freshCloseCalls = 0;
1326
+ const freshProvider: IntelligenceProvider = {
1327
+ id: 'port.close.notification-failure.recovery-provider',
1328
+ start(_submission, nextSink) {
1329
+ nextSink.complete('fresh-run');
1330
+ return ok(undefined);
1331
+ },
1332
+ cancel() {
1333
+ return ok(undefined);
1334
+ },
1335
+ async close() {
1336
+ freshCloseCalls += 1;
1337
+ },
1338
+ };
1339
+ const freshHost = bindIntelligencePort(
1340
+ freshChannel.port1 as unknown as IntelligenceMessagePort,
1341
+ createIntelligenceRuntime(freshProvider),
1342
+ );
1343
+ const freshClient = createIntelligencePortClient(
1344
+ freshProvider.id,
1345
+ freshChannel.port2 as unknown as IntelligenceMessagePort,
1346
+ );
1347
+ try {
1348
+ expect(freshClient.submit({ input: 'fresh binding' }).ok).toBe(true);
1349
+ let freshEvents: readonly ReturnType<typeof freshClient.poll>[number][] = [];
1350
+ for (let attempt = 0; attempt < 20 && freshEvents.length === 0; attempt += 1) {
1351
+ freshEvents = freshClient.poll();
1352
+ await new Promise((resolve) => setTimeout(resolve, 5));
1353
+ }
1354
+ expect(freshEvents).toEqual([
1355
+ expect.objectContaining({ type: 'completed', output: 'fresh-run' }),
1356
+ ]);
1357
+ await freshClient.close();
1358
+ await freshHost.close();
1359
+ expect(freshCloseCalls).toBe(1);
1360
+ } finally {
1361
+ await freshHost.close().catch(() => undefined);
1362
+ freshChannel.port2.close();
1363
+ }
1364
+ } finally {
1365
+ rawClientPort.close();
1366
+ await host.close().catch(() => undefined);
1367
+ }
1368
+ });
1369
+
1370
+ it('contains a synchronous Host poll response failure after the runtime has drained events', async () => {
1371
+ let sink: ActivitySink | undefined;
1372
+ let closeCalls = 0;
1373
+ let removeListenerCalls = 0;
1374
+ let portCloseCalls = 0;
1375
+ let hostListener:
1376
+ | ((event: MessageEvent<IntelligenceHostCommand | IntelligenceRealmMessage>) => void)
1377
+ | undefined;
1378
+ const posted: string[] = [];
1379
+ const provider: IntelligenceProvider = {
1380
+ id: 'port.host-poll-response-failure.provider',
1381
+ start(_submission, nextSink) {
1382
+ sink = nextSink;
1383
+ return ok(undefined);
1384
+ },
1385
+ cancel() {
1386
+ return ok(undefined);
1387
+ },
1388
+ async close() {
1389
+ closeCalls += 1;
1390
+ },
1391
+ };
1392
+ const runtime = createIntelligenceRuntime(provider, { limits: { maxPollEvents: 1 } });
1393
+ const hostPort: IntelligenceMessagePort = {
1394
+ postMessage(message) {
1395
+ posted.push(message.kind);
1396
+ if (message.kind === 'intelligence-events') {
1397
+ throw new Error('sentinel Host poll response publication failure');
1398
+ }
1399
+ },
1400
+ addEventListener(_type, listener) {
1401
+ hostListener = listener;
1402
+ },
1403
+ removeEventListener(_type, listener) {
1404
+ removeListenerCalls += Number(listener === hostListener);
1405
+ hostListener = undefined;
1406
+ },
1407
+ start() {},
1408
+ close() {
1409
+ portCloseCalls += 1;
1410
+ },
1411
+ };
1412
+ const host = bindIntelligencePort(hostPort, runtime);
1413
+ const activity = {
1414
+ id: activityId('host-poll-response-failure-activity'),
1415
+ session: {
1416
+ providerId: provider.id,
1417
+ id: 'host-poll-response-failure-session',
1418
+ },
1419
+ input: 'live poll response failure',
1420
+ } as const;
1421
+
1422
+ try {
1423
+ expect(runtime.accept(activity).ok).toBe(true);
1424
+ sink?.text('staged-before-host-response-fault');
1425
+ expect(hostListener).toBeDefined();
1426
+ expect(() =>
1427
+ hostListener?.({
1428
+ data: { kind: 'intelligence-poll', maxEvents: 1 },
1429
+ } as MessageEvent<IntelligenceHostCommand>),
1430
+ ).not.toThrow();
1431
+
1432
+ const closeTask = host.close();
1433
+ expect(host.close()).toBe(closeTask);
1434
+ await expect(closeTask).resolves.toBeUndefined();
1435
+ expect(posted).toEqual(['intelligence-events', 'intelligence-closed']);
1436
+ expect(closeCalls).toBe(1);
1437
+ expect(removeListenerCalls).toBe(1);
1438
+ expect(portCloseCalls).toBe(1);
1439
+ expect(runtime.poll()).toEqual([]);
1440
+ } finally {
1441
+ await host.close().catch(() => undefined);
1442
+ }
1443
+ });
1444
+
768
1445
  it('propagates direct Host binding close to a live realm client', async () => {
769
1446
  let sink: ActivitySink | undefined;
770
1447
  let starts = 0;